mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Compare commits
111 Commits
network-lo
...
introducti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aec948f18 | ||
|
|
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 | ||
|
|
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'
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,23 +14,28 @@ 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.
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
||||||
|
* 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,
|
* 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.
|
||||||
@@ -94,11 +100,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -339,12 +347,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 +356,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 +403,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 +480,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 +521,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ public class Group {
|
|||||||
SHARED // The group is visible and messages are shared
|
SHARED // The group is visible and messages are shared
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the group format.
|
||||||
|
*/
|
||||||
|
public static final int FORMAT_VERSION = 1;
|
||||||
|
|
||||||
private final GroupId id;
|
private final GroupId id;
|
||||||
private final ClientId clientId;
|
private final ClientId clientId;
|
||||||
private final byte[] descriptor;
|
private final 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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,51 @@ 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.
|
||||||
*/
|
*/
|
||||||
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.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
r.readListEnd();
|
r.readListEnd();
|
||||||
LOG.info("Received pseudonym");
|
LOG.info("Received pseudonym");
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
if (!crypto.verify(SIGNING_LABEL_EXCHANGE, nonce, publicKey, sig)) {
|
if (!crypto.verifySignature(sig, SIGNING_LABEL_EXCHANGE, nonce,
|
||||||
|
publicKey)) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Invalid signature");
|
LOG.info("Invalid signature");
|
||||||
throw new GeneralSecurityException();
|
throw new GeneralSecurityException();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,10 +302,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 +311,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 +495,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 +592,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 +633,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
|
||||||
@@ -637,6 +656,5 @@ interface Database<T> {
|
|||||||
/**
|
/**
|
||||||
* Stores the given transport keys, deleting any keys they have replaced.
|
* Stores the given transport keys, deleting any keys they have replaced.
|
||||||
*/
|
*/
|
||||||
void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
|
void updateTransportKeys(T txn, Collection<KeySet> keys) 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
|
||||||
@@ -586,8 +598,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 +607,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 +775,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 +789,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,38 +871,42 @@ 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<>();
|
Collection<KeySet> filtered = new ArrayList<>();
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
for (KeySet ks : keys) {
|
||||||
ContactId c = e.getKey();
|
TransportId t = ks.getTransportKeys().getTransportId();
|
||||||
TransportKeys k = e.getValue();
|
if (db.containsTransport(txn, t)) filtered.add(ks);
|
||||||
if (db.containsContact(txn, c)
|
|
||||||
&& db.containsTransport(txn, k.getTransportId())) {
|
|
||||||
filtered.put(c, k);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
db.updateTransportKeys(txn, filtered);
|
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,7 @@ 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 = 36;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
@@ -170,6 +172,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,37 +225,44 @@ 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),"
|
+ " PRIMARY KEY (transportId, keySetId, rotationPeriod),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
|
||||||
+ " 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 =
|
||||||
@@ -264,6 +277,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"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 +424,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);
|
||||||
@@ -423,6 +440,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
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);
|
||||||
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();
|
||||||
@@ -715,6 +733,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 +813,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 +874,105 @@ 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)"
|
||||||
|
+ " 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.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.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.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);
|
||||||
@@ -1663,11 +1757,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 +1767,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 +1787,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 +2131,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 +2141,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, rotationPeriod";
|
||||||
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 +2158,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 +2198,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 +2682,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 +2842,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 +2868,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 +2890,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 +2944,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTransportKeys(Connection txn,
|
public void updateTransportKeys(Connection txn, Collection<KeySet> keys)
|
||||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
for (KeySet ks : keys) {
|
||||||
try {
|
TransportKeys k = ks.getTransportKeys();
|
||||||
// Delete any existing incoming keys
|
removeTransportKeys(txn, k.getTransportId(), ks.getKeySetId());
|
||||||
String sql = "DELETE FROM incomingKeys"
|
addTransportKeys(txn, ks.getContactId(), k);
|
||||||
+ " WHERE contactId = ?"
|
|
||||||
+ " AND transportId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
|
||||||
ps.setInt(1, e.getKey().getInt());
|
|
||||||
ps.setString(2, e.getValue().getTransportId().getString());
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != keys.size())
|
|
||||||
throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
// Delete any existing outgoing keys
|
|
||||||
sql = "DELETE FROM outgoingKeys"
|
|
||||||
+ " WHERE contactId = ?"
|
|
||||||
+ " AND transportId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
|
||||||
ps.setInt(1, e.getKey().getInt());
|
|
||||||
ps.setString(2, e.getValue().getTransportId().getString());
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != keys.size())
|
|
||||||
throw new DbStateException();
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(ps);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
// Store the new keys
|
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
|
||||||
addTransportKeys(txn, e.getKey(), e.getValue());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ public class PropertiesModule {
|
|||||||
lifecycleManager.registerClient(transportPropertyManager);
|
lifecycleManager.registerClient(transportPropertyManager);
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
contactManager.registerAddContactHook(transportPropertyManager);
|
contactManager.registerContactHook(transportPropertyManager);
|
||||||
contactManager.registerRemoveContactHook(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;
|
||||||
@@ -40,7 +39,7 @@ 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, IncomingMessageHook {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
@@ -348,10 +347,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);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ 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
|
||||||
@@ -31,7 +31,7 @@ class GroupFactoryImpl implements GroupFactory {
|
|||||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
||||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
||||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION},
|
||||||
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
||||||
descriptor);
|
descriptor);
|
||||||
return new Group(new GroupId(hash), c, descriptor);
|
return new Group(new GroupId(hash), c, descriptor);
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ 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.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
|
||||||
@@ -32,11 +34,14 @@ 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];
|
byte[] versionBytes = new byte[] {FORMAT_VERSION};
|
||||||
|
// There's only one block, so the root hash is the hash of the block
|
||||||
|
byte[] rootHash = crypto.hash(BLOCK_LABEL, versionBytes, body);
|
||||||
|
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
byte[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
|
||||||
g.getBytes(), timeBytes, body);
|
timeBytes, rootHash);
|
||||||
MessageId id = new MessageId(hash);
|
MessageId id = new MessageId(idHash);
|
||||||
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);
|
||||||
|
|||||||
@@ -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<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.Random;
|
|||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
@@ -300,30 +301,34 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVerifySignature() throws Exception {
|
public void testVerifySignature() throws Exception {
|
||||||
|
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||||
byte[] publicKey = getRandomBytes(42);
|
byte[] publicKey = getRandomBytes(42);
|
||||||
byte[] bytes = expectToByteArray(list);
|
byte[] signed = expectToByteArray(list);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
oneOf(cryptoComponent).verifySignature(signature, label, signed,
|
||||||
|
publicKey);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.verifySignature(label, rawMessage, publicKey, list);
|
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVerifyWrongSignature() throws Exception {
|
public void testVerifyWrongSignature() throws Exception {
|
||||||
|
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||||
byte[] publicKey = getRandomBytes(42);
|
byte[] publicKey = getRandomBytes(42);
|
||||||
byte[] bytes = expectToByteArray(list);
|
byte[] signed = expectToByteArray(list);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
oneOf(cryptoComponent).verifySignature(signature, label, signed,
|
||||||
|
publicKey);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clientHelper.verifySignature(label, rawMessage, publicKey, list);
|
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||||
fail();
|
fail();
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
// expected
|
// expected
|
||||||
|
|||||||
@@ -143,9 +143,9 @@ public class EdSignatureTest extends SignatureTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
protected boolean verify(byte[] signature, String label, byte[] signed,
|
||||||
byte[] signature) throws GeneralSecurityException {
|
byte[] publicKey) throws GeneralSecurityException {
|
||||||
return crypto.verify(label, signedData, publicKey, signature);
|
return crypto.verifySignature(signature, label, signed, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -27,13 +28,13 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
private final TransportCrypto transportCrypto =
|
private final TransportCrypto transportCrypto =
|
||||||
new TransportCryptoImpl(crypto);
|
new TransportCryptoImpl(crypto);
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = getTransportId();
|
||||||
private final SecretKey master = getSecretKey();
|
private final SecretKey master = getSecretKey();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreDistinct() {
|
public void testKeysAreDistinct() {
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
assertAllDifferent(k);
|
assertAllDifferent(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,9 +42,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, false);
|
master, 123, false, true);
|
||||||
// Alice's incoming keys should equal Bob's outgoing keys
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -73,9 +74,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, false);
|
master, 123, false, true);
|
||||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||||
// in period 455
|
// in period 455
|
||||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||||
@@ -100,9 +101,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
public void testNextKeysMatchNextKeysOfContact() {
|
public void testNextKeysMatchNextKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, false);
|
master, 123, false, true);
|
||||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||||
// period 455
|
// period 455
|
||||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||||
@@ -127,20 +128,20 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
SecretKey master1 = getSecretKey();
|
SecretKey master1 = getSecretKey();
|
||||||
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master1, 123, true);
|
master1, 123, true, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTransportIdAffectsOutput() {
|
public void testTransportIdAffectsOutput() {
|
||||||
TransportId transportId1 = new TransportId("id1");
|
TransportId transportId1 = getTransportId();
|
||||||
assertFalse(transportId.getString().equals(transportId1.getString()));
|
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
||||||
master, 123, true);
|
master, 123, true, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,13 +126,13 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
byte[] signature = crypto.sign("test", message,
|
byte[] signature = crypto.sign("test", message,
|
||||||
privateKey.getEncoded());
|
privateKey.getEncoded());
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||||
signature));
|
publicKey.getEncoded()));
|
||||||
// Encode and parse the public key - no exceptions should be thrown
|
// Encode and parse the public key - no exceptions should be thrown
|
||||||
publicKey = parser.parsePublicKey(publicKey.getEncoded());
|
publicKey = parser.parsePublicKey(publicKey.getEncoded());
|
||||||
// Verify the signature again
|
// Verify the signature again
|
||||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||||
signature));
|
publicKey.getEncoded()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -146,15 +146,15 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
byte[] signature = crypto.sign("test", message,
|
byte[] signature = crypto.sign("test", message,
|
||||||
privateKey.getEncoded());
|
privateKey.getEncoded());
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||||
signature));
|
publicKey.getEncoded()));
|
||||||
// Encode and parse the private key - no exceptions should be thrown
|
// Encode and parse the private key - no exceptions should be thrown
|
||||||
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
|
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
|
||||||
// Sign the data again - the signatures should be the same
|
// Sign the data again - the signatures should be the same
|
||||||
byte[] signature1 = crypto.sign("test", message,
|
byte[] signature1 = crypto.sign("test", message,
|
||||||
privateKey.getEncoded());
|
privateKey.getEncoded());
|
||||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
assertTrue(crypto.verifySignature(signature1, "test", message,
|
||||||
signature1));
|
publicKey.getEncoded()));
|
||||||
assertArrayEquals(signature, signature1);
|
assertArrayEquals(signature, signature1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
|||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class MacTest extends BrambleTestCase {
|
public class MacTest extends BrambleTestCase {
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ public class MacTest extends BrambleTestCase {
|
|||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
assertArrayEquals(mac, mac1);
|
assertArrayEquals(mac, mac1);
|
||||||
|
assertTrue(crypto.verifyMac(mac, label1, key1, input1, input2, input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -40,6 +42,11 @@ public class MacTest extends BrambleTestCase {
|
|||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
|
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
|
// Each MAC should fail to verify with the other MAC's label
|
||||||
|
assertFalse(crypto.verifyMac(mac, label2, key1, input1, input2,
|
||||||
|
input3));
|
||||||
|
assertFalse(crypto.verifyMac(mac1, label1, key2, input1, input2,
|
||||||
|
input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -48,6 +55,11 @@ public class MacTest extends BrambleTestCase {
|
|||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
|
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
|
// Each MAC should fail to verify with the other MAC's key
|
||||||
|
assertFalse(crypto.verifyMac(mac, label1, key2, input1, input2,
|
||||||
|
input3));
|
||||||
|
assertFalse(crypto.verifyMac(mac1, label2, key1, input1, input2,
|
||||||
|
input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -57,6 +69,11 @@ public class MacTest extends BrambleTestCase {
|
|||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
|
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
|
// Each MAC should fail to verify with the other MAC's inputs
|
||||||
|
assertFalse(crypto.verifyMac(mac, label1, key2, input3, input2,
|
||||||
|
input1));
|
||||||
|
assertFalse(crypto.verifyMac(mac1, label1, key1, input1, input2,
|
||||||
|
input3));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
protected abstract byte[] sign(String label, byte[] toSign,
|
protected abstract byte[] sign(String label, byte[] toSign,
|
||||||
byte[] privateKey) throws GeneralSecurityException;
|
byte[] privateKey) throws GeneralSecurityException;
|
||||||
|
|
||||||
protected abstract boolean verify(String label, byte[] signedData,
|
protected abstract boolean verify(byte[] signature, String label,
|
||||||
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
|
byte[] signed, byte[] publicKey) throws GeneralSecurityException;
|
||||||
|
|
||||||
SignatureTest() {
|
SignatureTest() {
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
@@ -85,7 +85,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testSignatureVerification() throws Exception {
|
public void testSignatureVerification() throws Exception {
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertTrue(verify(label, inputBytes, publicKey, sig));
|
assertTrue(verify(sig, label, inputBytes, publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -95,7 +95,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||||
// calculate the signature with different key, should fail to verify
|
// calculate the signature with different key, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey2);
|
byte[] sig = sign(label, inputBytes, privateKey2);
|
||||||
assertFalse(verify(label, inputBytes, publicKey, sig));
|
assertFalse(verify(sig, label, inputBytes, publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -104,7 +104,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||||
// calculate the signature with different input, should fail to verify
|
// calculate the signature with different input, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertFalse(verify(label, inputBytes2, publicKey, sig));
|
assertFalse(verify(sig, label, inputBytes2, publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -113,7 +113,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
String label2 = StringUtils.getRandomString(42);
|
String label2 = StringUtils.getRandomString(42);
|
||||||
// calculate the signature with different label, should fail to verify
|
// calculate the signature with different label, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertFalse(verify(label2, inputBytes, publicKey, sig));
|
assertFalse(verify(sig, label2, inputBytes, publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,34 +44,36 @@ 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.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;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
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.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.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.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
|
||||||
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.UNKNOWN;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
@@ -100,27 +102,28 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final Contact contact;
|
private final Contact contact;
|
||||||
|
private final KeySetId keySetId;
|
||||||
|
|
||||||
public DatabaseComponentImplTest() {
|
public DatabaseComponentImplTest() {
|
||||||
clientId = new ClientId(getRandomString(123));
|
clientId = getClientId();
|
||||||
groupId = new GroupId(TestUtils.getRandomId());
|
group = getGroup(clientId);
|
||||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
groupId = group.getId();
|
||||||
group = new Group(groupId, clientId, descriptor);
|
|
||||||
author = getAuthor();
|
author = getAuthor();
|
||||||
localAuthor = getLocalAuthor();
|
localAuthor = getLocalAuthor();
|
||||||
messageId = new MessageId(TestUtils.getRandomId());
|
messageId = new MessageId(getRandomId());
|
||||||
messageId1 = new MessageId(TestUtils.getRandomId());
|
messageId1 = new MessageId(getRandomId());
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
size = 1234;
|
size = 1234;
|
||||||
raw = new byte[size];
|
raw = new byte[size];
|
||||||
message = new Message(messageId, groupId, timestamp, raw);
|
message = new Message(messageId, groupId, timestamp, raw);
|
||||||
metadata = new Metadata();
|
metadata = new Metadata();
|
||||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||||
transportId = new TransportId("id");
|
transportId = getTransportId();
|
||||||
maxLatency = Integer.MAX_VALUE;
|
maxLatency = Integer.MAX_VALUE;
|
||||||
contactId = new ContactId(234);
|
contactId = new ContactId(234);
|
||||||
contact = new Contact(contactId, author, localAuthor.getId(),
|
contact = new Contact(contactId, author, localAuthor.getId(),
|
||||||
true, true);
|
true, true);
|
||||||
|
keySetId = new KeySetId(345);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
||||||
@@ -282,11 +285,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(18).of(database).startTransaction();
|
exactly(17).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(18).of(database).containsContact(txn, contactId);
|
exactly(17).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(18).of(database).abortTransaction(txn);
|
exactly(17).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
shutdown);
|
shutdown);
|
||||||
@@ -301,6 +304,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.generateAck(transaction, contactId, 123);
|
db.generateAck(transaction, contactId, 123);
|
||||||
@@ -371,16 +384,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
|
||||||
try {
|
|
||||||
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
|
||||||
fail();
|
|
||||||
} catch (NoSuchContactException expected) {
|
|
||||||
// Expected
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.getGroupVisibility(transaction, contactId, groupId);
|
db.getGroupVisibility(transaction, contactId, groupId);
|
||||||
@@ -454,17 +457,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
|
||||||
try {
|
|
||||||
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
|
||||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
|
||||||
fail();
|
|
||||||
} catch (NoSuchContactException expected) {
|
|
||||||
// Expected
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.setGroupVisibility(transaction, contactId, groupId, SHARED);
|
db.setGroupVisibility(transaction, contactId, groupId, SHARED);
|
||||||
@@ -777,13 +769,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// endTransaction()
|
// endTransaction()
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// Check whether the transport is in the DB (which it's not)
|
// Check whether the transport is in the DB (which it's not)
|
||||||
exactly(4).of(database).startTransaction();
|
exactly(6).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(2).of(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
exactly(4).of(database).containsTransport(txn, transportId);
|
exactly(6).of(database).containsTransport(txn, transportId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(4).of(database).abortTransaction(txn);
|
exactly(6).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
shutdown);
|
shutdown);
|
||||||
@@ -798,6 +790,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchTransportException expected) {
|
||||||
|
// Expected
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.getTransportKeys(transaction, transportId);
|
db.getTransportKeys(transaction, transportId);
|
||||||
@@ -810,7 +812,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
db.incrementStreamCounter(transaction, transportId, keySetId);
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchTransportException expected) {
|
} catch (NoSuchTransportException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -830,7 +832,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
db.removeTransportKeys(transaction, transportId, keySetId);
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchTransportException expected) {
|
||||||
|
// Expected
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
db.setReorderingWindow(transaction, keySetId, transportId, 0, 0,
|
||||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchTransportException expected) {
|
} catch (NoSuchTransportException expected) {
|
||||||
@@ -907,7 +919,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateOffer() throws Exception {
|
public void testGenerateOffer() throws Exception {
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(getRandomId());
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
@@ -938,7 +950,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateRequest() throws Exception {
|
public void testGenerateRequest() throws Exception {
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(getRandomId());
|
||||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
@@ -1126,9 +1138,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceiveOffer() throws Exception {
|
public void testReceiveOffer() throws Exception {
|
||||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId1 = new MessageId(getRandomId());
|
||||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId2 = new MessageId(getRandomId());
|
||||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId3 = new MessageId(getRandomId());
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
@@ -1303,15 +1315,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testTransportKeys() throws Exception {
|
public void testTransportKeys() throws Exception {
|
||||||
TransportKeys transportKeys = createTransportKeys();
|
TransportKeys transportKeys = createTransportKeys();
|
||||||
Map<ContactId, TransportKeys> keys =
|
Collection<KeySet> keys =
|
||||||
singletonMap(contactId, transportKeys);
|
singletonList(new KeySet(keySetId, contactId, transportKeys));
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// startTransaction()
|
// startTransaction()
|
||||||
oneOf(database).startTransaction();
|
oneOf(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
// updateTransportKeys()
|
// updateTransportKeys()
|
||||||
oneOf(database).containsContact(txn, contactId);
|
|
||||||
will(returnValue(true));
|
|
||||||
oneOf(database).containsTransport(txn, transportId);
|
oneOf(database).containsTransport(txn, transportId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).updateTransportKeys(txn, keys);
|
oneOf(database).updateTransportKeys(txn, keys);
|
||||||
@@ -1337,22 +1347,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TransportKeys createTransportKeys() {
|
private TransportKeys createTransportKeys() {
|
||||||
SecretKey inPrevTagKey = TestUtils.getSecretKey();
|
SecretKey inPrevTagKey = getSecretKey();
|
||||||
SecretKey inPrevHeaderKey = TestUtils.getSecretKey();
|
SecretKey inPrevHeaderKey = getSecretKey();
|
||||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||||
1, 123, new byte[4]);
|
1, 123, new byte[4]);
|
||||||
SecretKey inCurrTagKey = TestUtils.getSecretKey();
|
SecretKey inCurrTagKey = getSecretKey();
|
||||||
SecretKey inCurrHeaderKey = TestUtils.getSecretKey();
|
SecretKey inCurrHeaderKey = getSecretKey();
|
||||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||||
2, 234, new byte[4]);
|
2, 234, new byte[4]);
|
||||||
SecretKey inNextTagKey = TestUtils.getSecretKey();
|
SecretKey inNextTagKey = getSecretKey();
|
||||||
SecretKey inNextHeaderKey = TestUtils.getSecretKey();
|
SecretKey inNextHeaderKey = getSecretKey();
|
||||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||||
3, 345, new byte[4]);
|
3, 345, new byte[4]);
|
||||||
SecretKey outCurrTagKey = TestUtils.getSecretKey();
|
SecretKey outCurrTagKey = getSecretKey();
|
||||||
SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
|
SecretKey outCurrHeaderKey = getSecretKey();
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||||
2, 456);
|
2, 456, true);
|
||||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1498,7 +1508,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void testMessageDependencies() throws Exception {
|
public void testMessageDependencies() throws Exception {
|
||||||
int shutdownHandle = 12345;
|
int shutdownHandle = 12345;
|
||||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId2 = new MessageId(getRandomId());
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// open()
|
// open()
|
||||||
oneOf(database).open(null);
|
oneOf(database).open(null);
|
||||||
@@ -1518,10 +1528,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// addMessageDependencies()
|
// addMessageDependencies()
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).addMessageDependency(txn, groupId, messageId,
|
oneOf(database).getMessageState(txn, messageId);
|
||||||
messageId1);
|
will(returnValue(DELIVERED));
|
||||||
oneOf(database).addMessageDependency(txn, groupId, messageId,
|
oneOf(database).addMessageDependency(txn, message, messageId1,
|
||||||
messageId2);
|
DELIVERED);
|
||||||
|
oneOf(database).addMessageDependency(txn, message, messageId2,
|
||||||
|
DELIVERED);
|
||||||
// getMessageDependencies()
|
// getMessageDependencies()
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
|
|||||||
@@ -572,8 +572,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
messageMeta.get(g.getId()).add(mm);
|
messageMeta.get(g.getId()).add(mm);
|
||||||
db.mergeMessageMetadata(txn, m.getId(), mm);
|
db.mergeMessageMetadata(txn, m.getId(), mm);
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
db.addMessageDependency(txn, g.getId(), m.getId(),
|
MessageId dependency =
|
||||||
pickRandom(groupMessages.get(g.getId())));
|
pickRandom(groupMessages.get(g.getId()));
|
||||||
|
db.addMessageDependency(txn, m, dependency, state);
|
||||||
}
|
}
|
||||||
groupMessages.get(g.getId()).add(m.getId());
|
groupMessages.get(g.getId()).add(m.getId());
|
||||||
}
|
}
|
||||||
@@ -598,8 +599,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
messageMeta.get(g.getId()).add(mm);
|
messageMeta.get(g.getId()).add(mm);
|
||||||
db.mergeMessageMetadata(txn, m.getId(), mm);
|
db.mergeMessageMetadata(txn, m.getId(), mm);
|
||||||
if (j > 0) {
|
if (j > 0) {
|
||||||
db.addMessageDependency(txn, g.getId(), m.getId(),
|
MessageId dependency =
|
||||||
pickRandom(groupMessages.get(g.getId())));
|
pickRandom(groupMessages.get(g.getId()));
|
||||||
|
db.addMessageDependency(txn, m, dependency, DELIVERED);
|
||||||
}
|
}
|
||||||
groupMessages.get(g.getId()).add(m.getId());
|
groupMessages.get(g.getId()).add(m.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,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;
|
||||||
import org.briarproject.bramble.system.SystemClock;
|
import org.briarproject.bramble.system.SystemClock;
|
||||||
@@ -34,7 +36,6 @@ import java.sql.Connection;
|
|||||||
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.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -42,23 +43,28 @@ import java.util.Random;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.emptyMap;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
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.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
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.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -86,12 +92,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
private final Message message;
|
private final Message message;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
|
private final KeySetId keySetId, keySetId1;
|
||||||
|
|
||||||
JdbcDatabaseTest() throws Exception {
|
JdbcDatabaseTest() throws Exception {
|
||||||
groupId = new GroupId(getRandomId());
|
clientId = getClientId();
|
||||||
clientId = new ClientId(getRandomString(123));
|
group = getGroup(clientId);
|
||||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
groupId = group.getId();
|
||||||
group = new Group(groupId, clientId, descriptor);
|
|
||||||
author = getAuthor();
|
author = getAuthor();
|
||||||
localAuthor = getLocalAuthor();
|
localAuthor = getLocalAuthor();
|
||||||
messageId = new MessageId(getRandomId());
|
messageId = new MessageId(getRandomId());
|
||||||
@@ -99,8 +105,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
size = 1234;
|
size = 1234;
|
||||||
raw = getRandomBytes(size);
|
raw = getRandomBytes(size);
|
||||||
message = new Message(messageId, groupId, timestamp, raw);
|
message = new Message(messageId, groupId, timestamp, raw);
|
||||||
transportId = new TransportId("id");
|
transportId = getTransportId();
|
||||||
contactId = new ContactId(1);
|
contactId = new ContactId(1);
|
||||||
|
keySetId = new KeySetId(1);
|
||||||
|
keySetId1 = new KeySetId(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract JdbcDatabase createDatabase(DatabaseConfig config,
|
protected abstract JdbcDatabase createDatabase(DatabaseConfig config,
|
||||||
@@ -190,9 +198,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// The contact has not seen the message, so it should be sendable
|
// The contact has not seen the message, so it should be sendable
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Changing the status to seen = true should make the message unsendable
|
// Changing the status to seen = true should make the message unsendable
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
@@ -228,9 +236,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Marking the message delivered should make it sendable
|
// Marking the message delivered should make it sendable
|
||||||
db.setMessageState(txn, messageId, DELIVERED);
|
db.setMessageState(txn, messageId, DELIVERED);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Marking the message invalid should make it unsendable
|
// Marking the message invalid should make it unsendable
|
||||||
db.setMessageState(txn, messageId, INVALID);
|
db.setMessageState(txn, messageId, INVALID);
|
||||||
@@ -279,9 +287,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Sharing the group should make the message sendable
|
// Sharing the group should make the message sendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// Unsharing the group should make the message unsendable
|
// Unsharing the group should make the message unsendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||||
@@ -324,9 +332,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Sharing the message should make it sendable
|
// Sharing the message should make it sendable
|
||||||
db.setMessageShared(txn, messageId);
|
db.setMessageShared(txn, messageId);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -352,7 +360,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// The message is just the right size to send
|
// The message is just the right size to send
|
||||||
ids = db.getMessagesToSend(txn, contactId, size);
|
ids = db.getMessagesToSend(txn, contactId, size);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -384,7 +392,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1));
|
||||||
|
|
||||||
// Both message IDs should have been removed
|
// Both message IDs should have been removed
|
||||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
||||||
contactId, 1234));
|
contactId, 1234));
|
||||||
|
|
||||||
// Raise the ack flag again
|
// Raise the ack flag again
|
||||||
@@ -415,7 +423,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Retrieve the message from the database and mark it as sent
|
// Retrieve the message from the database and mark it as sent
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
ONE_MEGABYTE);
|
ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE);
|
db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE);
|
||||||
|
|
||||||
// The message should no longer be sendable
|
// The message should no longer be sendable
|
||||||
@@ -626,31 +634,31 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// The group should not be visible to the contact
|
// The group should not be visible to the contact
|
||||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(Collections.emptyMap(),
|
assertEquals(emptyMap(),
|
||||||
db.getGroupVisibility(txn, groupId));
|
db.getGroupVisibility(txn, groupId));
|
||||||
|
|
||||||
// Make the group visible to the contact
|
// Make the group visible to the contact
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(Collections.singletonMap(contactId, false),
|
assertEquals(singletonMap(contactId, false),
|
||||||
db.getGroupVisibility(txn, groupId));
|
db.getGroupVisibility(txn, groupId));
|
||||||
|
|
||||||
// Share the group with the contact
|
// Share the group with the contact
|
||||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||||
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(Collections.singletonMap(contactId, true),
|
assertEquals(singletonMap(contactId, true),
|
||||||
db.getGroupVisibility(txn, groupId));
|
db.getGroupVisibility(txn, groupId));
|
||||||
|
|
||||||
// Unshare the group with the contact
|
// Unshare the group with the contact
|
||||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(Collections.singletonMap(contactId, false),
|
assertEquals(singletonMap(contactId, false),
|
||||||
db.getGroupVisibility(txn, groupId));
|
db.getGroupVisibility(txn, groupId));
|
||||||
|
|
||||||
// Make the group invisible again
|
// Make the group invisible again
|
||||||
db.removeGroupVisibility(txn, contactId, groupId);
|
db.removeGroupVisibility(txn, contactId, groupId);
|
||||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(Collections.emptyMap(),
|
assertEquals(emptyMap(),
|
||||||
db.getGroupVisibility(txn, groupId));
|
db.getGroupVisibility(txn, groupId));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -660,48 +668,125 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testTransportKeys() throws Exception {
|
public void testTransportKeys() throws Exception {
|
||||||
TransportKeys keys = createTransportKeys();
|
TransportKeys keys = createTransportKeys();
|
||||||
|
TransportKeys keys1 = createTransportKeys();
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Initially there should be no transport keys in the database
|
// Initially there should be no transport keys in the database
|
||||||
assertEquals(Collections.emptyMap(),
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
db.getTransportKeys(txn, transportId));
|
|
||||||
|
|
||||||
// Add the contact, the transport and the transport keys
|
// Add the contact, the transport and the transport keys
|
||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
true, true));
|
true, true));
|
||||||
db.addTransport(txn, transportId, 123);
|
db.addTransport(txn, transportId, 123);
|
||||||
db.addTransportKeys(txn, contactId, keys);
|
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||||
|
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||||
|
|
||||||
// Retrieve the transport keys
|
// Retrieve the transport keys
|
||||||
Map<ContactId, TransportKeys> newKeys =
|
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
|
||||||
db.getTransportKeys(txn, transportId);
|
assertEquals(2, allKeys.size());
|
||||||
assertEquals(1, newKeys.size());
|
for (KeySet ks : allKeys) {
|
||||||
Entry<ContactId, TransportKeys> e =
|
assertEquals(contactId, ks.getContactId());
|
||||||
newKeys.entrySet().iterator().next();
|
if (ks.getKeySetId().equals(keySetId)) {
|
||||||
assertEquals(contactId, e.getKey());
|
assertKeysEquals(keys, ks.getTransportKeys());
|
||||||
TransportKeys k = e.getValue();
|
} else {
|
||||||
assertEquals(transportId, k.getTransportId());
|
assertEquals(keySetId1, ks.getKeySetId());
|
||||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||||
k.getPreviousIncomingKeys());
|
}
|
||||||
assertKeysEquals(keys.getCurrentIncomingKeys(),
|
}
|
||||||
k.getCurrentIncomingKeys());
|
|
||||||
assertKeysEquals(keys.getNextIncomingKeys(),
|
|
||||||
k.getNextIncomingKeys());
|
|
||||||
assertKeysEquals(keys.getCurrentOutgoingKeys(),
|
|
||||||
k.getCurrentOutgoingKeys());
|
|
||||||
|
|
||||||
// Removing the contact should remove the transport keys
|
// Removing the contact should remove the transport keys
|
||||||
db.removeContact(txn, contactId);
|
db.removeContact(txn, contactId);
|
||||||
assertEquals(Collections.emptyMap(),
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
db.getTransportKeys(txn, transportId));
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnboundTransportKeys() throws Exception {
|
||||||
|
TransportKeys keys = createTransportKeys();
|
||||||
|
TransportKeys keys1 = createTransportKeys();
|
||||||
|
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Initially there should be no transport keys in the database
|
||||||
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
|
|
||||||
|
// Add the contact, the transport and the unbound transport keys
|
||||||
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
|
true, true));
|
||||||
|
db.addTransport(txn, transportId, 123);
|
||||||
|
assertEquals(keySetId, db.addTransportKeys(txn, null, keys));
|
||||||
|
assertEquals(keySetId1, db.addTransportKeys(txn, null, keys1));
|
||||||
|
|
||||||
|
// Retrieve the transport keys
|
||||||
|
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
|
||||||
|
assertEquals(2, allKeys.size());
|
||||||
|
for (KeySet ks : allKeys) {
|
||||||
|
assertNull(ks.getContactId());
|
||||||
|
if (ks.getKeySetId().equals(keySetId)) {
|
||||||
|
assertKeysEquals(keys, ks.getTransportKeys());
|
||||||
|
} else {
|
||||||
|
assertEquals(keySetId1, ks.getKeySetId());
|
||||||
|
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the first set of transport keys
|
||||||
|
db.bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||||
|
|
||||||
|
// Retrieve the keys again - the first set should be bound
|
||||||
|
allKeys = db.getTransportKeys(txn, transportId);
|
||||||
|
assertEquals(2, allKeys.size());
|
||||||
|
for (KeySet ks : allKeys) {
|
||||||
|
if (ks.getKeySetId().equals(keySetId)) {
|
||||||
|
assertEquals(contactId, ks.getContactId());
|
||||||
|
assertKeysEquals(keys, ks.getTransportKeys());
|
||||||
|
} else {
|
||||||
|
assertEquals(keySetId1, ks.getKeySetId());
|
||||||
|
assertNull(ks.getContactId());
|
||||||
|
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the unbound transport keys
|
||||||
|
db.removeTransportKeys(txn, transportId, keySetId1);
|
||||||
|
|
||||||
|
// Retrieve the keys again - the second set should be gone
|
||||||
|
allKeys = db.getTransportKeys(txn, transportId);
|
||||||
|
assertEquals(1, allKeys.size());
|
||||||
|
KeySet ks = allKeys.iterator().next();
|
||||||
|
assertEquals(keySetId, ks.getKeySetId());
|
||||||
|
assertEquals(contactId, ks.getContactId());
|
||||||
|
assertKeysEquals(keys, ks.getTransportKeys());
|
||||||
|
|
||||||
|
// Removing the transport should remove the remaining transport keys
|
||||||
|
db.removeTransport(txn, transportId);
|
||||||
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeysEquals(TransportKeys expected,
|
||||||
|
TransportKeys actual) {
|
||||||
|
assertEquals(expected.getTransportId(), actual.getTransportId());
|
||||||
|
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||||
|
assertKeysEquals(expected.getPreviousIncomingKeys(),
|
||||||
|
actual.getPreviousIncomingKeys());
|
||||||
|
assertKeysEquals(expected.getCurrentIncomingKeys(),
|
||||||
|
actual.getCurrentIncomingKeys());
|
||||||
|
assertKeysEquals(expected.getNextIncomingKeys(),
|
||||||
|
actual.getNextIncomingKeys());
|
||||||
|
assertKeysEquals(expected.getCurrentOutgoingKeys(),
|
||||||
|
actual.getCurrentOutgoingKeys());
|
||||||
|
}
|
||||||
|
|
||||||
private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) {
|
private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) {
|
||||||
assertArrayEquals(expected.getTagKey().getBytes(),
|
assertArrayEquals(expected.getTagKey().getBytes(),
|
||||||
actual.getTagKey().getBytes());
|
actual.getTagKey().getBytes());
|
||||||
@@ -719,6 +804,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
actual.getHeaderKey().getBytes());
|
actual.getHeaderKey().getBytes());
|
||||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||||
assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
|
assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
|
||||||
|
assertEquals(expected.isActive(), actual.isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -735,18 +821,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
true, true));
|
true, true));
|
||||||
db.addTransport(txn, transportId, 123);
|
db.addTransport(txn, transportId, 123);
|
||||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
db.updateTransportKeys(txn,
|
||||||
|
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||||
|
|
||||||
// Increment the stream counter twice and retrieve the transport keys
|
// Increment the stream counter twice and retrieve the transport keys
|
||||||
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||||
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||||
Map<ContactId, TransportKeys> newKeys =
|
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||||
db.getTransportKeys(txn, transportId);
|
|
||||||
assertEquals(1, newKeys.size());
|
assertEquals(1, newKeys.size());
|
||||||
Entry<ContactId, TransportKeys> e =
|
KeySet ks = newKeys.iterator().next();
|
||||||
newKeys.entrySet().iterator().next();
|
assertEquals(keySetId, ks.getKeySetId());
|
||||||
assertEquals(contactId, e.getKey());
|
assertEquals(contactId, ks.getContactId());
|
||||||
TransportKeys k = e.getValue();
|
TransportKeys k = ks.getTransportKeys();
|
||||||
assertEquals(transportId, k.getTransportId());
|
assertEquals(transportId, k.getTransportId());
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
||||||
@@ -771,19 +857,19 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
true, true));
|
true, true));
|
||||||
db.addTransport(txn, transportId, 123);
|
db.addTransport(txn, transportId, 123);
|
||||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
db.updateTransportKeys(txn,
|
||||||
|
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||||
|
|
||||||
// Update the reordering window and retrieve the transport keys
|
// Update the reordering window and retrieve the transport keys
|
||||||
new Random().nextBytes(bitmap);
|
new Random().nextBytes(bitmap);
|
||||||
db.setReorderingWindow(txn, contactId, transportId, rotationPeriod,
|
db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod,
|
||||||
base + 1, bitmap);
|
base + 1, bitmap);
|
||||||
Map<ContactId, TransportKeys> newKeys =
|
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||||
db.getTransportKeys(txn, transportId);
|
|
||||||
assertEquals(1, newKeys.size());
|
assertEquals(1, newKeys.size());
|
||||||
Entry<ContactId, TransportKeys> e =
|
KeySet ks = newKeys.iterator().next();
|
||||||
newKeys.entrySet().iterator().next();
|
assertEquals(keySetId, ks.getKeySetId());
|
||||||
assertEquals(contactId, e.getKey());
|
assertEquals(contactId, ks.getContactId());
|
||||||
TransportKeys k = e.getValue();
|
TransportKeys k = ks.getTransportKeys();
|
||||||
assertEquals(transportId, k.getTransportId());
|
assertEquals(transportId, k.getTransportId());
|
||||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
assertEquals(rotationPeriod, inCurr.getRotationPeriod());
|
assertEquals(rotationPeriod, inCurr.getRotationPeriod());
|
||||||
@@ -830,18 +916,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addLocalAuthor(txn, localAuthor);
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
Collection<ContactId> contacts =
|
Collection<ContactId> contacts =
|
||||||
db.getContacts(txn, localAuthor.getId());
|
db.getContacts(txn, localAuthor.getId());
|
||||||
assertEquals(Collections.emptyList(), contacts);
|
assertEquals(emptyList(), contacts);
|
||||||
|
|
||||||
// Add a contact associated with the local author
|
// Add a contact associated with the local author
|
||||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
true, true));
|
true, true));
|
||||||
contacts = db.getContacts(txn, localAuthor.getId());
|
contacts = db.getContacts(txn, localAuthor.getId());
|
||||||
assertEquals(Collections.singletonList(contactId), contacts);
|
assertEquals(singletonList(contactId), contacts);
|
||||||
|
|
||||||
// Remove the local author - the contact should be removed
|
// Remove the local author - the contact should be removed
|
||||||
db.removeLocalAuthor(txn, localAuthor.getId());
|
db.removeLocalAuthor(txn, localAuthor.getId());
|
||||||
contacts = db.getContacts(txn, localAuthor.getId());
|
contacts = db.getContacts(txn, localAuthor.getId());
|
||||||
assertEquals(Collections.emptyList(), contacts);
|
assertEquals(emptyList(), contacts);
|
||||||
assertFalse(db.containsContact(txn, contactId));
|
assertFalse(db.containsContact(txn, contactId));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -1227,6 +1313,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
MessageId messageId4 = new MessageId(getRandomId());
|
MessageId messageId4 = new MessageId(getRandomId());
|
||||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||||
Message message2 = new Message(messageId2, groupId, timestamp, raw);
|
Message message2 = new Message(messageId2, groupId, timestamp, raw);
|
||||||
|
Message message3 = new Message(messageId3, groupId, timestamp, raw);
|
||||||
|
Message message4 = new Message(messageId4, groupId, timestamp, raw);
|
||||||
|
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
@@ -1234,21 +1322,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Add a group and some messages
|
// Add a group and some messages
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, PENDING, true, contactId);
|
db.addMessage(txn, message, PENDING, true, contactId);
|
||||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
db.addMessage(txn, message1, PENDING, true, contactId);
|
||||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
db.addMessage(txn, message2, INVALID, true, contactId);
|
||||||
|
|
||||||
// Add dependencies
|
// Add dependencies
|
||||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||||
db.addMessageDependency(txn, groupId, messageId, messageId2);
|
db.addMessageDependency(txn, message, messageId2, PENDING);
|
||||||
db.addMessageDependency(txn, groupId, messageId1, messageId3);
|
db.addMessageDependency(txn, message1, messageId3, PENDING);
|
||||||
db.addMessageDependency(txn, groupId, messageId2, messageId4);
|
db.addMessageDependency(txn, message2, messageId4, INVALID);
|
||||||
|
|
||||||
Map<MessageId, State> dependencies;
|
Map<MessageId, State> dependencies;
|
||||||
|
|
||||||
// Retrieve dependencies for root
|
// Retrieve dependencies for root
|
||||||
dependencies = db.getMessageDependencies(txn, messageId);
|
dependencies = db.getMessageDependencies(txn, messageId);
|
||||||
assertEquals(2, dependencies.size());
|
assertEquals(2, dependencies.size());
|
||||||
assertEquals(DELIVERED, dependencies.get(messageId1));
|
assertEquals(PENDING, dependencies.get(messageId1));
|
||||||
assertEquals(INVALID, dependencies.get(messageId2));
|
assertEquals(INVALID, dependencies.get(messageId2));
|
||||||
|
|
||||||
// Retrieve dependencies for message 1
|
// Retrieve dependencies for message 1
|
||||||
@@ -1281,10 +1369,24 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(1, dependents.size());
|
assertEquals(1, dependents.size());
|
||||||
assertEquals(PENDING, dependents.get(messageId));
|
assertEquals(PENDING, dependents.get(messageId));
|
||||||
|
|
||||||
|
// Message 3 is missing, so it has no dependents
|
||||||
|
dependents = db.getMessageDependents(txn, messageId3);
|
||||||
|
assertEquals(0, dependents.size());
|
||||||
|
|
||||||
|
// Add message 3
|
||||||
|
db.addMessage(txn, message3, UNKNOWN, false, contactId);
|
||||||
|
|
||||||
// Message 3 has message 1 as a dependent
|
// Message 3 has message 1 as a dependent
|
||||||
dependents = db.getMessageDependents(txn, messageId3);
|
dependents = db.getMessageDependents(txn, messageId3);
|
||||||
assertEquals(1, dependents.size());
|
assertEquals(1, dependents.size());
|
||||||
assertEquals(DELIVERED, dependents.get(messageId1));
|
assertEquals(PENDING, dependents.get(messageId1));
|
||||||
|
|
||||||
|
// Message 4 is missing, so it has no dependents
|
||||||
|
dependents = db.getMessageDependents(txn, messageId4);
|
||||||
|
assertEquals(0, dependents.size());
|
||||||
|
|
||||||
|
// Add message 4
|
||||||
|
db.addMessage(txn, message4, UNKNOWN, false, contactId);
|
||||||
|
|
||||||
// Message 4 has message 2 as a dependent
|
// Message 4 has message 2 as a dependent
|
||||||
dependents = db.getMessageDependents(txn, messageId4);
|
dependents = db.getMessageDependents(txn, messageId4);
|
||||||
@@ -1305,9 +1407,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, PENDING, true, contactId);
|
db.addMessage(txn, message, PENDING, true, contactId);
|
||||||
|
|
||||||
// Add a second group
|
// Add a second group
|
||||||
GroupId groupId1 = new GroupId(getRandomId());
|
Group group1 = getGroup(clientId);
|
||||||
Group group1 = new Group(groupId1, clientId,
|
GroupId groupId1 = group1.getId();
|
||||||
getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
|
||||||
db.addGroup(txn, group1);
|
db.addGroup(txn, group1);
|
||||||
|
|
||||||
// Add a message to the second group
|
// Add a message to the second group
|
||||||
@@ -1324,16 +1425,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
||||||
|
|
||||||
// Add dependencies between the messages
|
// Add dependencies between the messages
|
||||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||||
db.addMessageDependency(txn, groupId, messageId, messageId2);
|
db.addMessageDependency(txn, message, messageId2, PENDING);
|
||||||
db.addMessageDependency(txn, groupId, messageId, messageId3);
|
db.addMessageDependency(txn, message, messageId3, PENDING);
|
||||||
|
|
||||||
// Retrieve the dependencies for the root
|
// Retrieve the dependencies for the root
|
||||||
Map<MessageId, State> dependencies;
|
Map<MessageId, State> dependencies;
|
||||||
dependencies = db.getMessageDependencies(txn, messageId);
|
dependencies = db.getMessageDependencies(txn, messageId);
|
||||||
|
|
||||||
// The cross-group dependency should have state INVALID
|
// The cross-group dependency should have state UNKNOWN
|
||||||
assertEquals(INVALID, dependencies.get(messageId1));
|
assertEquals(UNKNOWN, dependencies.get(messageId1));
|
||||||
|
|
||||||
// The missing dependency should have state UNKNOWN
|
// The missing dependency should have state UNKNOWN
|
||||||
assertEquals(UNKNOWN, dependencies.get(messageId2));
|
assertEquals(UNKNOWN, dependencies.get(messageId2));
|
||||||
@@ -1345,8 +1446,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Map<MessageId, State> dependents;
|
Map<MessageId, State> dependents;
|
||||||
dependents = db.getMessageDependents(txn, messageId1);
|
dependents = db.getMessageDependents(txn, messageId1);
|
||||||
|
|
||||||
// The cross-group dependent should have its real state
|
// The cross-group dependent should be excluded
|
||||||
assertEquals(PENDING, dependents.get(messageId));
|
assertFalse(dependents.containsKey(messageId));
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1411,9 +1512,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||||
|
|
||||||
// Introduce dependencies between the messages
|
// Introduce dependencies between the messages
|
||||||
db.addMessageDependency(txn, groupId, mId1, mId2);
|
db.addMessageDependency(txn, m1, mId2, DELIVERED);
|
||||||
db.addMessageDependency(txn, groupId, mId3, mId1);
|
db.addMessageDependency(txn, m3, mId1, DELIVERED);
|
||||||
db.addMessageDependency(txn, groupId, mId4, mId3);
|
db.addMessageDependency(txn, m4, mId3, DELIVERED);
|
||||||
|
|
||||||
// Retrieve messages to be shared
|
// Retrieve messages to be shared
|
||||||
Collection<MessageId> result = db.getMessagesToShare(txn);
|
Collection<MessageId> result = db.getMessagesToShare(txn);
|
||||||
@@ -1544,9 +1645,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// The message should be sendable
|
// The message should be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
ONE_MEGABYTE);
|
ONE_MEGABYTE);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||||
assertEquals(Collections.singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
// The raw message should not be null
|
// The raw message should not be null
|
||||||
assertNotNull(db.getRawMessage(txn, messageId));
|
assertNotNull(db.getRawMessage(txn, messageId));
|
||||||
@@ -1719,7 +1820,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
SecretKey outCurrTagKey = getSecretKey();
|
SecretKey outCurrTagKey = getSecretKey();
|
||||||
SecretKey outCurrHeaderKey = getSecretKey();
|
SecretKey outCurrHeaderKey = getSecretKey();
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||||
2, 456);
|
2, 456, true);
|
||||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RE
|
|||||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
import static org.briarproject.bramble.api.keyagreement.RecordTypes.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;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
|||||||
private final TransportConnectionWriter transportConnectionWriter =
|
private final TransportConnectionWriter transportConnectionWriter =
|
||||||
context.mock(TransportConnectionWriter.class);
|
context.mock(TransportConnectionWriter.class);
|
||||||
|
|
||||||
private final TransportId transportId = new TransportId("test");
|
private final TransportId transportId = getTransportId();
|
||||||
private final KeyAgreementConnection keyAgreementConnection =
|
private final KeyAgreementConnection keyAgreementConnection =
|
||||||
new KeyAgreementConnection(duplexTransportConnection, transportId);
|
new KeyAgreementConnection(duplexTransportConnection, transportId);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
@@ -29,8 +30,8 @@ public class ConnectionRegistryImplTest extends BrambleTestCase {
|
|||||||
public ConnectionRegistryImplTest() {
|
public ConnectionRegistryImplTest() {
|
||||||
contactId = new ContactId(1);
|
contactId = new ContactId(1);
|
||||||
contactId1 = new ContactId(2);
|
contactId1 = new ContactId(2);
|
||||||
transportId = new TransportId("id");
|
transportId = getTransportId();
|
||||||
transportId1 = new TransportId("id1");
|
transportId1 = getTransportId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import java.util.Arrays;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
public class PluginManagerImplTest extends BrambleTestCase {
|
public class PluginManagerImplTest extends BrambleTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -46,21 +48,21 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
|||||||
SimplexPluginFactory simplexFactory =
|
SimplexPluginFactory simplexFactory =
|
||||||
context.mock(SimplexPluginFactory.class);
|
context.mock(SimplexPluginFactory.class);
|
||||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||||
TransportId simplexId = new TransportId("simplex");
|
TransportId simplexId = getTransportId();
|
||||||
SimplexPluginFactory simplexFailFactory =
|
SimplexPluginFactory simplexFailFactory =
|
||||||
context.mock(SimplexPluginFactory.class, "simplexFailFactory");
|
context.mock(SimplexPluginFactory.class, "simplexFailFactory");
|
||||||
SimplexPlugin simplexFailPlugin =
|
SimplexPlugin simplexFailPlugin =
|
||||||
context.mock(SimplexPlugin.class, "simplexFailPlugin");
|
context.mock(SimplexPlugin.class, "simplexFailPlugin");
|
||||||
TransportId simplexFailId = new TransportId("simplex1");
|
TransportId simplexFailId = getTransportId();
|
||||||
|
|
||||||
// Two duplex plugin factories: one creates a plugin, the other fails
|
// Two duplex plugin factories: one creates a plugin, the other fails
|
||||||
DuplexPluginFactory duplexFactory =
|
DuplexPluginFactory duplexFactory =
|
||||||
context.mock(DuplexPluginFactory.class);
|
context.mock(DuplexPluginFactory.class);
|
||||||
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
||||||
TransportId duplexId = new TransportId("duplex");
|
TransportId duplexId = getTransportId();
|
||||||
DuplexPluginFactory duplexFailFactory =
|
DuplexPluginFactory duplexFailFactory =
|
||||||
context.mock(DuplexPluginFactory.class, "duplexFailFactory");
|
context.mock(DuplexPluginFactory.class, "duplexFailFactory");
|
||||||
TransportId duplexFailId = new TransportId("duplex1");
|
TransportId duplexFailId = getTransportId();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(simplexPlugin).getId();
|
allowing(simplexPlugin).getId();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
public class PollerTest extends BrambleMockTestCase {
|
public class PollerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ public class PollerTest extends BrambleMockTestCase {
|
|||||||
private final SecureRandom random;
|
private final SecureRandom random;
|
||||||
|
|
||||||
private final Executor ioExecutor = new ImmediateExecutor();
|
private final Executor ioExecutor = new ImmediateExecutor();
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = getTransportId();
|
||||||
private final ContactId contactId = new ContactId(234);
|
private final ContactId contactId = new ContactId(234);
|
||||||
private final int pollingInterval = 60 * 1000;
|
private final int pollingInterval = 60 * 1000;
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
@@ -64,7 +65,7 @@ public class PollerTest extends BrambleMockTestCase {
|
|||||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||||
SimplexPlugin simplexPlugin1 =
|
SimplexPlugin simplexPlugin1 =
|
||||||
context.mock(SimplexPlugin.class, "simplexPlugin1");
|
context.mock(SimplexPlugin.class, "simplexPlugin1");
|
||||||
TransportId simplexId1 = new TransportId("simplex1");
|
TransportId simplexId1 = getTransportId();
|
||||||
List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
|
List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
|
||||||
simplexPlugin1);
|
simplexPlugin1);
|
||||||
TransportConnectionWriter simplexWriter =
|
TransportConnectionWriter simplexWriter =
|
||||||
@@ -72,7 +73,7 @@ public class PollerTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
// Two duplex plugins: one supports polling, the other doesn't
|
// Two duplex plugins: one supports polling, the other doesn't
|
||||||
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
||||||
TransportId duplexId = new TransportId("duplex");
|
TransportId duplexId = getTransportId();
|
||||||
DuplexPlugin duplexPlugin1 =
|
DuplexPlugin duplexPlugin1 =
|
||||||
context.mock(DuplexPlugin.class, "duplexPlugin1");
|
context.mock(DuplexPlugin.class, "duplexPlugin1");
|
||||||
List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
|
List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
|
||||||
@@ -349,7 +350,6 @@ public class PollerTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
||||||
Plugin plugin = context.mock(Plugin.class);
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
List<ContactId> connected = Collections.singletonList(contactId);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(plugin).getId();
|
allowing(plugin).getId();
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import java.util.Map;
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
@@ -51,7 +51,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
context.mock(ContactGroupFactory.class);
|
context.mock(ContactGroupFactory.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
private final Group localGroup = getGroup();
|
private final Group localGroup = getGroup(CLIENT_ID);
|
||||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||||
private final BdfDictionary fooPropertiesDict = BdfDictionary.of(
|
private final BdfDictionary fooPropertiesDict = BdfDictionary.of(
|
||||||
new BdfEntry("fooKey1", "fooValue1"),
|
new BdfEntry("fooKey1", "fooValue1"),
|
||||||
@@ -90,7 +90,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Contact contact1 = getContact(true);
|
Contact contact1 = getContact(true);
|
||||||
Contact contact2 = getContact(true);
|
Contact contact2 = getContact(true);
|
||||||
List<Contact> contacts = Arrays.asList(contact1, contact2);
|
List<Contact> contacts = Arrays.asList(contact1, contact2);
|
||||||
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
Group contactGroup1 = getGroup(CLIENT_ID);
|
||||||
|
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
@@ -143,7 +144,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup(CLIENT_ID);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Create the group and share it with the contact
|
// Create the group and share it with the contact
|
||||||
@@ -171,7 +172,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup(CLIENT_ID);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
@@ -306,7 +307,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testStoresRemotePropertiesWithVersion0() throws Exception {
|
public void testStoresRemotePropertiesWithVersion0() throws Exception {
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup(CLIENT_ID);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Map<TransportId, TransportProperties> properties =
|
Map<TransportId, TransportProperties> properties =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
@@ -399,6 +400,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(fooUpdate));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
fooPropertiesDict);
|
||||||
|
will(returnValue(fooProperties));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
@@ -417,8 +421,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Contact contact3 = getContact(true);
|
Contact contact3 = getContact(true);
|
||||||
List<Contact> contacts =
|
List<Contact> contacts =
|
||||||
Arrays.asList(contact1, contact2, contact3);
|
Arrays.asList(contact1, contact2, contact3);
|
||||||
Group contactGroup2 = getGroup();
|
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||||
Group contactGroup3 = getGroup();
|
Group contactGroup3 = getGroup(CLIENT_ID);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata3 =
|
Map<MessageId, BdfDictionary> messageMetadata3 =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
// A remote update for another transport should be ignored
|
// A remote update for another transport should be ignored
|
||||||
@@ -466,6 +470,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(messageMetadata3));
|
will(returnValue(messageMetadata3));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(fooUpdate));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
fooPropertiesDict);
|
||||||
|
will(returnValue(fooProperties));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
@@ -501,6 +508,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, updateId);
|
oneOf(clientHelper).getMessageAsList(txn, updateId);
|
||||||
will(returnValue(update));
|
will(returnValue(update));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
fooPropertiesDict);
|
||||||
|
will(returnValue(fooProperties));
|
||||||
// Properties are unchanged so we're done
|
// Properties are unchanged so we're done
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
@@ -514,7 +524,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testMergingNewPropertiesCreatesUpdate() throws Exception {
|
public void testMergingNewPropertiesCreatesUpdate() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup(CLIENT_ID);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(false);
|
oneOf(db).startTransaction(false);
|
||||||
@@ -549,7 +559,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception {
|
public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup(CLIENT_ID);
|
||||||
BdfDictionary oldMetadata = BdfDictionary.of(
|
BdfDictionary oldMetadata = BdfDictionary.of(
|
||||||
new BdfEntry("transportId", "foo"),
|
new BdfEntry("transportId", "foo"),
|
||||||
new BdfEntry("version", 1),
|
new BdfEntry("version", 1),
|
||||||
@@ -561,9 +571,12 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
MessageId contactGroupUpdateId = new MessageId(getRandomId());
|
MessageId contactGroupUpdateId = new MessageId(getRandomId());
|
||||||
Map<MessageId, BdfDictionary> contactGroupMessageMetadata =
|
Map<MessageId, BdfDictionary> contactGroupMessageMetadata =
|
||||||
Collections.singletonMap(contactGroupUpdateId, oldMetadata);
|
Collections.singletonMap(contactGroupUpdateId, oldMetadata);
|
||||||
BdfList oldUpdate = BdfList.of("foo", 1, BdfDictionary.of(
|
TransportProperties oldProperties = new TransportProperties();
|
||||||
|
oldProperties.put("fooKey1", "oldFooValue1");
|
||||||
|
BdfDictionary oldPropertiesDict = BdfDictionary.of(
|
||||||
new BdfEntry("fooKey1", "oldFooValue1")
|
new BdfEntry("fooKey1", "oldFooValue1")
|
||||||
));
|
);
|
||||||
|
BdfList oldUpdate = BdfList.of("foo", 1, oldPropertiesDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(false);
|
oneOf(db).startTransaction(false);
|
||||||
@@ -574,6 +587,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(localGroupMessageMetadata));
|
will(returnValue(localGroupMessageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId);
|
||||||
will(returnValue(oldUpdate));
|
will(returnValue(oldUpdate));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
oldPropertiesDict);
|
||||||
|
will(returnValue(oldProperties));
|
||||||
// Store the merged properties in the local group, version 2
|
// Store the merged properties in the local group, version 2
|
||||||
expectStoreMessage(txn, localGroup.getId(), "foo",
|
expectStoreMessage(txn, localGroup.getId(), "foo",
|
||||||
fooPropertiesDict, 2, true, false);
|
fooPropertiesDict, 2, true, false);
|
||||||
@@ -600,12 +616,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
t.mergeLocalProperties(new TransportId("foo"), fooProperties);
|
t.mergeLocalProperties(new TransportId("foo"), fooProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Group getGroup() {
|
|
||||||
GroupId g = new GroupId(getRandomId());
|
|
||||||
byte[] descriptor = getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH);
|
|
||||||
return new Group(g, CLIENT_ID, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Contact getContact(boolean active) {
|
private Contact getContact(boolean active) {
|
||||||
ContactId c = new ContactId(nextContactId++);
|
ContactId c = new ContactId(nextContactId++);
|
||||||
return new Contact(c, getAuthor(), localAuthor.getId(),
|
return new Contact(c, getAuthor(), localAuthor.getId(),
|
||||||
@@ -643,8 +653,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
// Retrieve and parse the latest local properties
|
// Retrieve and parse the latest local properties
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(fooUpdate));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
fooPropertiesDict);
|
||||||
|
will(returnValue(fooProperties));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, barVersion3);
|
oneOf(clientHelper).getMessageAsList(txn, barVersion3);
|
||||||
will(returnValue(barUpdate));
|
will(returnValue(barUpdate));
|
||||||
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
barPropertiesDict);
|
||||||
|
will(returnValue(barProperties));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,80 +3,78 @@ package org.briarproject.bramble.properties;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
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.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.jmock.Expectations;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
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.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class TransportPropertyValidatorTest extends BrambleTestCase {
|
public class TransportPropertyValidatorTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||||
|
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final BdfDictionary bdfDictionary;
|
private final BdfDictionary bdfDictionary;
|
||||||
|
private final TransportProperties transportProperties;
|
||||||
private final Group group;
|
private final Group group;
|
||||||
private final Message message;
|
private final Message message;
|
||||||
private final TransportPropertyValidator tpv;
|
private final TransportPropertyValidator tpv;
|
||||||
|
|
||||||
public TransportPropertyValidatorTest() {
|
public TransportPropertyValidatorTest() {
|
||||||
transportId = new TransportId("test");
|
transportId = getTransportId();
|
||||||
bdfDictionary = new BdfDictionary();
|
bdfDictionary = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
||||||
|
transportProperties = new TransportProperties();
|
||||||
|
transportProperties.put("foo", "bar");
|
||||||
|
|
||||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
group = getGroup(getClientId());
|
||||||
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
|
message = getMessage(group.getId());
|
||||||
byte[] descriptor = TestUtils.getRandomBytes(12);
|
|
||||||
group = new Group(groupId, clientId, descriptor);
|
|
||||||
|
|
||||||
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
byte[] body = TestUtils.getRandomBytes(123);
|
|
||||||
message = new Message(messageId, groupId, timestamp, body);
|
|
||||||
|
|
||||||
Mockery context = new Mockery();
|
|
||||||
ClientHelper clientHelper = context.mock(ClientHelper.class);
|
|
||||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||||
Clock clock = context.mock(Clock.class);
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
tpv = new TransportPropertyValidator(clientHelper, metadataEncoder,
|
tpv = new TransportPropertyValidator(clientHelper, metadataEncoder,
|
||||||
clock);
|
clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateProperMessage() throws IOException {
|
public void testValidateProperMessage() throws IOException {
|
||||||
|
|
||||||
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
|
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
|
||||||
|
|
||||||
BdfDictionary result = tpv.validateMessage(message, group, body)
|
context.checking(new Expectations() {{
|
||||||
.getDictionary();
|
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||||
|
bdfDictionary);
|
||||||
|
will(returnValue(transportProperties));
|
||||||
|
}});
|
||||||
|
|
||||||
assertEquals("test", result.getString("transportId"));
|
BdfDictionary result =
|
||||||
|
tpv.validateMessage(message, group, body).getDictionary();
|
||||||
|
assertEquals(transportId.getString(), result.getString("transportId"));
|
||||||
assertEquals(4, result.getLong("version").longValue());
|
assertEquals(4, result.getLong("version").longValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testValidateWrongVersionValue() throws IOException {
|
public void testValidateWrongVersionValue() throws IOException {
|
||||||
|
|
||||||
BdfList body = BdfList.of(transportId.getString(), -1, bdfDictionary);
|
BdfList body = BdfList.of(transportId.getString(), -1, bdfDictionary);
|
||||||
tpv.validateMessage(message, group, body);
|
tpv.validateMessage(message, group, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testValidateWrongVersionType() throws IOException {
|
public void testValidateWrongVersionType() throws IOException {
|
||||||
|
|
||||||
BdfList body = BdfList.of(transportId.getString(), bdfDictionary,
|
BdfList body = BdfList.of(transportId.getString(), bdfDictionary,
|
||||||
bdfDictionary);
|
bdfDictionary);
|
||||||
tpv.validateMessage(message, group, body);
|
tpv.validateMessage(message, group, body);
|
||||||
@@ -84,27 +82,15 @@ public class TransportPropertyValidatorTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testValidateLongTransportId() throws IOException {
|
public void testValidateLongTransportId() throws IOException {
|
||||||
|
|
||||||
String wrongTransportIdString =
|
String wrongTransportIdString =
|
||||||
StringUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
||||||
BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
|
BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
|
||||||
tpv.validateMessage(message, group, body);
|
tpv.validateMessage(message, group, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testValidateEmptyTransportId() throws IOException {
|
public void testValidateEmptyTransportId() throws IOException {
|
||||||
|
|
||||||
BdfList body = BdfList.of("", 4, bdfDictionary);
|
BdfList body = BdfList.of("", 4, bdfDictionary);
|
||||||
tpv.validateMessage(message, group, body);
|
tpv.validateMessage(message, group, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testValidateTooManyProperties() throws IOException {
|
|
||||||
|
|
||||||
BdfDictionary d = new BdfDictionary();
|
|
||||||
for (int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT + 1; i++)
|
|
||||||
d.put(String.valueOf(i), i);
|
|
||||||
BdfList body = BdfList.of(transportId.getString(), 4, d);
|
|
||||||
tpv.validateMessage(message, group, body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ import javax.inject.Inject;
|
|||||||
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.transport.TransportConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -73,13 +74,13 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
|
||||||
contactId = new ContactId(234);
|
contactId = new ContactId(234);
|
||||||
transportId = new TransportId("id");
|
transportId = getTransportId();
|
||||||
// Create the transport keys
|
// Create the transport keys
|
||||||
tagKey = TestUtils.getSecretKey();
|
tagKey = TestUtils.getSecretKey();
|
||||||
headerKey = TestUtils.getSecretKey();
|
headerKey = TestUtils.getSecretKey();
|
||||||
streamNumber = 123;
|
streamNumber = 123;
|
||||||
// Create a group
|
// Create a group
|
||||||
ClientId clientId = new ClientId(getRandomString(123));
|
ClientId clientId = getClientId();
|
||||||
int clientVersion = 1234567890;
|
int clientVersion = 1234567890;
|
||||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||||
Group group = groupFactory.createGroup(clientId, clientVersion,
|
Group group = groupFactory.createGroup(clientId, clientVersion,
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ import org.briarproject.bramble.api.sync.ValidationManager.State;
|
|||||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -38,6 +36,9 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
|
|||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.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.test.TestUtils.getClientId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
|
||||||
public class ValidationManagerImplTest extends BrambleMockTestCase {
|
public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -51,14 +52,12 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final Executor dbExecutor = new ImmediateExecutor();
|
private final Executor dbExecutor = new ImmediateExecutor();
|
||||||
private final Executor validationExecutor = new ImmediateExecutor();
|
private final Executor validationExecutor = new ImmediateExecutor();
|
||||||
private final ClientId clientId =
|
private final ClientId clientId = getClientId();
|
||||||
new ClientId(StringUtils.getRandomString(5));
|
private final MessageId messageId = new MessageId(getRandomId());
|
||||||
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
private final MessageId messageId1 = new MessageId(getRandomId());
|
||||||
private final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
private final MessageId messageId2 = new MessageId(getRandomId());
|
||||||
private final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
private final Group group = getGroup(clientId);
|
||||||
private final GroupId groupId = new GroupId(TestUtils.getRandomId());
|
private final GroupId groupId = group.getId();
|
||||||
private final byte[] descriptor = new byte[32];
|
|
||||||
private final Group group = new Group(groupId, clientId, descriptor);
|
|
||||||
private final long timestamp = System.currentTimeMillis();
|
private final long timestamp = System.currentTimeMillis();
|
||||||
private final byte[] raw = new byte[123];
|
private final byte[] raw = new byte[123];
|
||||||
private final Message message = new Message(messageId, groupId, timestamp,
|
private final Message message = new Message(messageId, groupId, timestamp,
|
||||||
@@ -716,8 +715,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecursiveInvalidation() throws Exception {
|
public void testRecursiveInvalidation() throws Exception {
|
||||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId3 = new MessageId(getRandomId());
|
||||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId4 = new MessageId(getRandomId());
|
||||||
Map<MessageId, State> twoDependents = new LinkedHashMap<>();
|
Map<MessageId, State> twoDependents = new LinkedHashMap<>();
|
||||||
twoDependents.put(messageId1, PENDING);
|
twoDependents.put(messageId1, PENDING);
|
||||||
twoDependents.put(messageId2, PENDING);
|
twoDependents.put(messageId2, PENDING);
|
||||||
@@ -819,8 +818,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPendingDependentsGetDelivered() throws Exception {
|
public void testPendingDependentsGetDelivered() throws Exception {
|
||||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId3 = new MessageId(getRandomId());
|
||||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
MessageId messageId4 = new MessageId(getRandomId());
|
||||||
Message message3 = new Message(messageId3, groupId, timestamp,
|
Message message3 = new Message(messageId3, groupId, timestamp,
|
||||||
raw);
|
raw);
|
||||||
Message message4 = new Message(messageId4, groupId, timestamp,
|
Message message4 = new Message(messageId4, groupId, timestamp,
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import javax.annotation.Nullable;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class TestPluginConfigModule {
|
public class TestPluginConfigModule {
|
||||||
|
|
||||||
public static final TransportId TRANSPORT_ID = new TransportId("id");
|
public static final TransportId TRANSPORT_ID = getTransportId();
|
||||||
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
package org.briarproject.bramble.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
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;
|
||||||
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.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
|
|
||||||
public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -21,17 +23,27 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
|||||||
protected final MetadataEncoder metadataEncoder =
|
protected final MetadataEncoder metadataEncoder =
|
||||||
context.mock(MetadataEncoder.class);
|
context.mock(MetadataEncoder.class);
|
||||||
protected final Clock clock = context.mock(Clock.class);
|
protected final Clock clock = context.mock(Clock.class);
|
||||||
protected final AuthorFactory authorFactory =
|
|
||||||
context.mock(AuthorFactory.class);
|
|
||||||
|
|
||||||
protected final MessageId messageId = new MessageId(getRandomId());
|
protected final Group group = getGroup(getClientId());
|
||||||
protected final GroupId groupId = new GroupId(getRandomId());
|
protected final GroupId groupId = group.getId();
|
||||||
protected final long timestamp = 1234567890 * 1000L;
|
protected final byte[] descriptor = group.getDescriptor();
|
||||||
protected final byte[] raw = getRandomBytes(123);
|
protected final Message message = getMessage(groupId);
|
||||||
protected final Message message =
|
protected final MessageId messageId = message.getId();
|
||||||
new Message(messageId, groupId, timestamp, raw);
|
protected final long timestamp = message.getTimestamp();
|
||||||
protected final ClientId clientId = new ClientId(getRandomString(123));
|
protected final byte[] raw = message.getRaw();
|
||||||
protected final byte[] descriptor = getRandomBytes(123);
|
protected final Author author = getAuthor();
|
||||||
protected final Group group = new Group(groupId, clientId, descriptor);
|
protected final BdfList authorList = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
|
||||||
}
|
protected void expectParseAuthor(BdfList authorList, Author author)
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).parseAndValidateAuthor(authorList);
|
||||||
|
will(returnValue(author));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,51 +12,51 @@ import org.briarproject.bramble.api.identity.AuthorId;
|
|||||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.lib.concurrent.DeterministicExecutor;
|
import org.jmock.lib.concurrent.DeterministicExecutor;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class KeyManagerImplTest extends BrambleTestCase {
|
public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final Mockery context = new Mockery();
|
|
||||||
private final KeyManagerImpl keyManager;
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
||||||
private final TransportKeyManagerFactory transportKeyManagerFactory =
|
private final TransportKeyManagerFactory transportKeyManagerFactory =
|
||||||
context.mock(TransportKeyManagerFactory.class);
|
context.mock(TransportKeyManagerFactory.class);
|
||||||
private final TransportKeyManager transportKeyManager =
|
private final TransportKeyManager transportKeyManager =
|
||||||
context.mock(TransportKeyManager.class);
|
context.mock(TransportKeyManager.class);
|
||||||
|
|
||||||
private final DeterministicExecutor executor = new DeterministicExecutor();
|
private final DeterministicExecutor executor = new DeterministicExecutor();
|
||||||
private final Transaction txn = new Transaction(null, false);
|
private final Transaction txn = new Transaction(null, false);
|
||||||
private final ContactId contactId = new ContactId(42);
|
private final ContactId contactId = new ContactId(123);
|
||||||
private final ContactId inactiveContactId = new ContactId(43);
|
private final ContactId inactiveContactId = new ContactId(234);
|
||||||
private final TransportId transportId = new TransportId("tId");
|
private final KeySetId keySetId = new KeySetId(345);
|
||||||
private final TransportId unknownTransportId = new TransportId("id");
|
private final TransportId transportId = getTransportId();
|
||||||
|
private final TransportId unknownTransportId = getTransportId();
|
||||||
private final StreamContext streamContext =
|
private final StreamContext streamContext =
|
||||||
new StreamContext(contactId, transportId, getSecretKey(),
|
new StreamContext(contactId, transportId, getSecretKey(),
|
||||||
getSecretKey(), 1);
|
getSecretKey(), 1);
|
||||||
private final byte[] tag = getRandomBytes(TAG_LENGTH);
|
private final byte[] tag = getRandomBytes(TAG_LENGTH);
|
||||||
|
|
||||||
public KeyManagerImplTest() {
|
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
|
||||||
keyManager = new KeyManagerImpl(db, executor, pluginConfig,
|
pluginConfig, transportKeyManagerFactory);
|
||||||
transportKeyManagerFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void testStartService() throws Exception {
|
public void testStartService() throws Exception {
|
||||||
@@ -70,8 +70,8 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
true, false));
|
true, false));
|
||||||
SimplexPluginFactory pluginFactory =
|
SimplexPluginFactory pluginFactory =
|
||||||
context.mock(SimplexPluginFactory.class);
|
context.mock(SimplexPluginFactory.class);
|
||||||
Collection<SimplexPluginFactory> factories = Collections
|
Collection<SimplexPluginFactory> factories =
|
||||||
.singletonList(pluginFactory);
|
singletonList(pluginFactory);
|
||||||
int maxLatency = 1337;
|
int maxLatency = 1337;
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -110,7 +110,22 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
|
keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
|
||||||
context.assertIsSatisfied();
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddUnboundKeys() throws Exception {
|
||||||
|
SecretKey secretKey = getSecretKey();
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
boolean alice = new Random().nextBoolean();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(transportKeyManager).addUnboundKeys(txn, secretKey,
|
||||||
|
timestamp, alice);
|
||||||
|
will(returnValue(keySetId));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(singletonMap(transportId, keySetId),
|
||||||
|
keyManager.addUnboundKeys(txn, secretKey, timestamp, alice));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -138,7 +153,6 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(streamContext,
|
assertEquals(streamContext,
|
||||||
keyManager.getStreamContext(contactId, transportId));
|
keyManager.getStreamContext(contactId, transportId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,7 +175,6 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(streamContext,
|
assertEquals(streamContext,
|
||||||
keyManager.getStreamContext(transportId, tag));
|
keyManager.getStreamContext(transportId, tag));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -175,8 +188,6 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
keyManager.eventOccurred(event);
|
keyManager.eventOccurred(event);
|
||||||
executor.runUntilIdle();
|
executor.runUntilIdle();
|
||||||
assertEquals(null, keyManager.getStreamContext(contactId, transportId));
|
assertEquals(null, keyManager.getStreamContext(contactId, transportId));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -196,8 +207,5 @@ public class KeyManagerImplTest extends BrambleTestCase {
|
|||||||
keyManager.eventOccurred(event);
|
keyManager.eventOccurred(event);
|
||||||
assertEquals(streamContext,
|
assertEquals(streamContext,
|
||||||
keyManager.getStreamContext(inactiveContactId, transportId));
|
keyManager.getStreamContext(inactiveContactId, transportId));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
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.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.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
@@ -22,23 +24,25 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@@ -50,11 +54,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
context.mock(ScheduledExecutorService.class);
|
context.mock(ScheduledExecutorService.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = getTransportId();
|
||||||
private final long maxLatency = 30 * 1000; // 30 seconds
|
private final long maxLatency = 30 * 1000; // 30 seconds
|
||||||
private final long rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
private final long rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
private final ContactId contactId = new ContactId(123);
|
private final ContactId contactId = new ContactId(123);
|
||||||
private final ContactId contactId1 = new ContactId(234);
|
private final ContactId contactId1 = new ContactId(234);
|
||||||
|
private final KeySetId keySetId = new KeySetId(345);
|
||||||
|
private final KeySetId keySetId1 = new KeySetId(456);
|
||||||
|
private final KeySetId keySetId2 = new KeySetId(567);
|
||||||
private final SecretKey tagKey = TestUtils.getSecretKey();
|
private final SecretKey tagKey = TestUtils.getSecretKey();
|
||||||
private final SecretKey headerKey = TestUtils.getSecretKey();
|
private final SecretKey headerKey = TestUtils.getSecretKey();
|
||||||
private final SecretKey masterKey = TestUtils.getSecretKey();
|
private final SecretKey masterKey = TestUtils.getSecretKey();
|
||||||
@@ -62,12 +69,16 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedAtStartup() throws Exception {
|
public void testKeysAreRotatedAtStartup() throws Exception {
|
||||||
Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
|
TransportKeys shouldRotate = createTransportKeys(900, 0, true);
|
||||||
TransportKeys shouldRotate = createTransportKeys(900, 0);
|
TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true);
|
||||||
TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
|
TransportKeys shouldRotate1 = createTransportKeys(999, 0, false);
|
||||||
loaded.put(contactId, shouldRotate);
|
Collection<KeySet> loaded = asList(
|
||||||
loaded.put(contactId1, shouldNotRotate);
|
new KeySet(keySetId, contactId, shouldRotate),
|
||||||
TransportKeys rotated = createTransportKeys(1000, 0);
|
new KeySet(keySetId1, contactId1, shouldNotRotate),
|
||||||
|
new KeySet(keySetId2, null, shouldRotate1)
|
||||||
|
);
|
||||||
|
TransportKeys rotated = createTransportKeys(1000, 0, true);
|
||||||
|
TransportKeys rotated1 = createTransportKeys(1000, 0, false);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -82,6 +93,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(rotated));
|
will(returnValue(rotated));
|
||||||
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
|
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
|
||||||
will(returnValue(shouldNotRotate));
|
will(returnValue(shouldNotRotate));
|
||||||
|
oneOf(transportCrypto).rotateTransportKeys(shouldRotate1, 1000);
|
||||||
|
will(returnValue(rotated1));
|
||||||
// Encode the tags (3 sets per contact)
|
// Encode the tags (3 sets per contact)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(6).of(transportCrypto).encodeTag(
|
exactly(6).of(transportCrypto).encodeTag(
|
||||||
@@ -90,8 +103,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Save the keys that were rotated
|
// Save the keys that were rotated
|
||||||
oneOf(db).updateTransportKeys(txn,
|
oneOf(db).updateTransportKeys(txn, asList(
|
||||||
Collections.singletonMap(contactId, rotated));
|
new KeySet(keySetId, contactId, rotated),
|
||||||
|
new KeySet(keySetId2, null, rotated1))
|
||||||
|
);
|
||||||
// Schedule key rotation at the start of the next rotation period
|
// Schedule key rotation at the start of the next rotation period
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with(rotationPeriodLength - 1), with(MILLISECONDS));
|
with(rotationPeriodLength - 1), with(MILLISECONDS));
|
||||||
@@ -101,18 +116,19 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
transportKeyManager.start(txn);
|
transportKeyManager.start(txn);
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedWhenAddingContact() throws Exception {
|
public void testKeysAreRotatedWhenAddingContact() throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
boolean alice = random.nextBoolean();
|
||||||
TransportKeys transportKeys = createTransportKeys(999, 0);
|
TransportKeys transportKeys = createTransportKeys(999, 0, true);
|
||||||
TransportKeys rotated = createTransportKeys(1000, 0);
|
TransportKeys rotated = createTransportKeys(1000, 0, true);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||||
999, alice);
|
999, alice, true);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (1 ms after start of rotation period 1000)
|
// Get the current time (1 ms after start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
@@ -129,6 +145,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, rotated);
|
oneOf(db).addTransportKeys(txn, contactId, rotated);
|
||||||
|
will(returnValue(keySetId));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
@@ -138,6 +155,39 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = rotationPeriodLength * 1000 - 1;
|
long timestamp = rotationPeriodLength * 1000 - 1;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeysAreRotatedWhenAddingUnboundKeys() throws Exception {
|
||||||
|
boolean alice = random.nextBoolean();
|
||||||
|
TransportKeys transportKeys = createTransportKeys(999, 0, false);
|
||||||
|
TransportKeys rotated = createTransportKeys(1000, 0, false);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||||
|
999, alice, false);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Get the current time (1 ms after start of rotation period 1000)
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(rotationPeriodLength * 1000 + 1));
|
||||||
|
// Rotate the transport keys
|
||||||
|
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
|
will(returnValue(rotated));
|
||||||
|
// Save the keys
|
||||||
|
oneOf(db).addTransportKeys(txn, null, rotated);
|
||||||
|
will(returnValue(keySetId));
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
|
maxLatency);
|
||||||
|
// The timestamp is 1 ms before the start of rotation period 1000
|
||||||
|
long timestamp = rotationPeriodLength * 1000 - 1;
|
||||||
|
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||||
|
masterKey, timestamp, alice));
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -149,6 +199,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -157,29 +208,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
boolean alice = random.nextBoolean();
|
boolean alice = random.nextBoolean();
|
||||||
// The stream counter has been exhausted
|
// The stream counter has been exhausted
|
||||||
TransportKeys transportKeys = createTransportKeys(1000,
|
TransportKeys transportKeys = createTransportKeys(1000,
|
||||||
MAX_32_BIT_UNSIGNED + 1);
|
MAX_32_BIT_UNSIGNED + 1, true);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
|
||||||
1000, alice);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Get the current time (the start of rotation period 1000)
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
|
||||||
// Encode the tags (3 sets)
|
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
|
||||||
with(any(byte[].class)), with(tagKey),
|
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Save the keys
|
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
|
||||||
}});
|
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
@@ -188,6 +220,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,30 +229,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
boolean alice = random.nextBoolean();
|
boolean alice = random.nextBoolean();
|
||||||
// The stream counter can be used one more time before being exhausted
|
// The stream counter can be used one more time before being exhausted
|
||||||
TransportKeys transportKeys = createTransportKeys(1000,
|
TransportKeys transportKeys = createTransportKeys(1000,
|
||||||
MAX_32_BIT_UNSIGNED);
|
MAX_32_BIT_UNSIGNED, true);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
|
||||||
1000, alice);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Get the current time (the start of rotation period 1000)
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
|
||||||
// Encode the tags (3 sets)
|
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
|
||||||
with(any(byte[].class)), with(tagKey),
|
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Save the keys
|
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
|
||||||
// Increment the stream counter
|
// Increment the stream counter
|
||||||
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
|
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
@@ -230,6 +247,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
// The first request should return a stream context
|
// The first request should return a stream context
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
StreamContext ctx = transportKeyManager.getStreamContext(txn,
|
StreamContext ctx = transportKeyManager.getStreamContext(txn,
|
||||||
contactId);
|
contactId);
|
||||||
assertNotNull(ctx);
|
assertNotNull(ctx);
|
||||||
@@ -239,6 +257,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(headerKey, ctx.getHeaderKey());
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
|
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
|
||||||
// The second request should return null, the counter is exhausted
|
// The second request should return null, the counter is exhausted
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,29 +265,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testIncomingStreamContextIsNullIfTagIsNotFound()
|
public void testIncomingStreamContextIsNullIfTagIsNotFound()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
boolean alice = random.nextBoolean();
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
|
||||||
1000, alice);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Get the current time (the start of rotation period 1000)
|
|
||||||
oneOf(clock).currentTimeMillis();
|
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
|
||||||
// Encode the tags (3 sets)
|
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
|
||||||
with(any(byte[].class)), with(tagKey),
|
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
|
||||||
}
|
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
|
||||||
will(returnValue(transportKeys));
|
|
||||||
// Save the keys
|
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
|
||||||
}});
|
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
@@ -277,6 +277,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
// The tag should not be recognised
|
||||||
assertNull(transportKeyManager.getStreamContext(txn,
|
assertNull(transportKeyManager.getStreamContext(txn,
|
||||||
new byte[TAG_LENGTH]));
|
new byte[TAG_LENGTH]));
|
||||||
}
|
}
|
||||||
@@ -284,14 +286,15 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testTagIsNotRecognisedTwice() throws Exception {
|
public void testTagIsNotRecognisedTwice() throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
boolean alice = random.nextBoolean();
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
// Keep a copy of the tags
|
// Keep a copy of the tags
|
||||||
List<byte[]> tags = new ArrayList<>();
|
List<byte[]> tags = new ArrayList<>();
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||||
1000, alice);
|
1000, alice, true);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (the start of rotation period 1000)
|
// Get the current time (the start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
@@ -308,13 +311,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
|
will(returnValue(keySetId));
|
||||||
// Encode a new tag after sliding the window
|
// Encode a new tag after sliding the window
|
||||||
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||||
with(tagKey), with(PROTOCOL_VERSION),
|
with(tagKey), with(PROTOCOL_VERSION),
|
||||||
with((long) REORDERING_WINDOW_SIZE));
|
with((long) REORDERING_WINDOW_SIZE));
|
||||||
will(new EncodeTagAction(tags));
|
will(new EncodeTagAction(tags));
|
||||||
// Save the reordering window (previous rotation period, base 1)
|
// Save the reordering window (previous rotation period, base 1)
|
||||||
oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
|
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||||
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -325,6 +329,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
// Use the first tag (previous rotation period, stream number 0)
|
// Use the first tag (previous rotation period, stream number 0)
|
||||||
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||||
byte[] tag = tags.get(0);
|
byte[] tag = tags.get(0);
|
||||||
@@ -344,10 +349,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
|
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||||
Map<ContactId, TransportKeys> loaded =
|
Collection<KeySet> loaded =
|
||||||
Collections.singletonMap(contactId, transportKeys);
|
singletonList(new KeySet(keySetId, contactId, transportKeys));
|
||||||
TransportKeys rotated = createTransportKeys(1001, 0);
|
TransportKeys rotated = createTransportKeys(1001, 0, true);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Transaction txn1 = new Transaction(null, false);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
|
|
||||||
@@ -393,7 +398,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
// Save the keys that were rotated
|
// Save the keys that were rotated
|
||||||
oneOf(db).updateTransportKeys(txn1,
|
oneOf(db).updateTransportKeys(txn1,
|
||||||
Collections.singletonMap(contactId, rotated));
|
singletonList(new KeySet(keySetId, contactId, rotated)));
|
||||||
// Schedule key rotation at the start of the next rotation period
|
// Schedule key rotation at the start of the next rotation period
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with(rotationPeriodLength), with(MILLISECONDS));
|
with(rotationPeriodLength), with(MILLISECONDS));
|
||||||
@@ -406,10 +411,197 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
transportKeyManager.start(txn);
|
transportKeyManager.start(txn);
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBindingAndActivatingKeys() throws Exception {
|
||||||
|
boolean alice = random.nextBoolean();
|
||||||
|
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// When the keys are bound, encode the tags (3 sets)
|
||||||
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
|
exactly(3).of(transportCrypto).encodeTag(
|
||||||
|
with(any(byte[].class)), with(tagKey),
|
||||||
|
with(PROTOCOL_VERSION), with(i));
|
||||||
|
will(new EncodeTagAction());
|
||||||
|
}
|
||||||
|
// Save the key binding
|
||||||
|
oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||||
|
// Activate the keys
|
||||||
|
oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
|
||||||
|
// Increment the stream counter
|
||||||
|
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
|
maxLatency);
|
||||||
|
// The timestamp is at the start of rotation period 1000
|
||||||
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
|
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||||
|
masterKey, timestamp, alice));
|
||||||
|
// The keys are unbound so no stream context should be returned
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
transportKeyManager.bindKeys(txn, contactId, keySetId);
|
||||||
|
// The keys are inactive so no stream context should be returned
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
transportKeyManager.activateKeys(txn, keySetId);
|
||||||
|
// The keys are active so a stream context should be returned
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
StreamContext ctx = transportKeyManager.getStreamContext(txn,
|
||||||
|
contactId);
|
||||||
|
assertNotNull(ctx);
|
||||||
|
assertEquals(contactId, ctx.getContactId());
|
||||||
|
assertEquals(transportId, ctx.getTransportId());
|
||||||
|
assertEquals(tagKey, ctx.getTagKey());
|
||||||
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
|
assertEquals(0L, ctx.getStreamNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecognisingTagActivatesOutgoingKeys() throws Exception {
|
||||||
|
boolean alice = random.nextBoolean();
|
||||||
|
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
// Keep a copy of the tags
|
||||||
|
List<byte[]> tags = new ArrayList<>();
|
||||||
|
|
||||||
|
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// When the keys are bound, encode the tags (3 sets)
|
||||||
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
|
exactly(3).of(transportCrypto).encodeTag(
|
||||||
|
with(any(byte[].class)), with(tagKey),
|
||||||
|
with(PROTOCOL_VERSION), with(i));
|
||||||
|
will(new EncodeTagAction(tags));
|
||||||
|
}
|
||||||
|
// Save the key binding
|
||||||
|
oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||||
|
// Encode a new tag after sliding the window
|
||||||
|
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||||
|
with(tagKey), with(PROTOCOL_VERSION),
|
||||||
|
with((long) REORDERING_WINDOW_SIZE));
|
||||||
|
will(new EncodeTagAction(tags));
|
||||||
|
// Save the reordering window (previous rotation period, base 1)
|
||||||
|
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||||
|
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
|
// Activate the keys
|
||||||
|
oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
|
||||||
|
// Increment the stream counter
|
||||||
|
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
|
maxLatency);
|
||||||
|
// The timestamp is at the start of rotation period 1000
|
||||||
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
|
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||||
|
masterKey, timestamp, alice));
|
||||||
|
transportKeyManager.bindKeys(txn, contactId, keySetId);
|
||||||
|
// The keys are inactive so no stream context should be returned
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
// Recognising an incoming tag should activate the outgoing keys
|
||||||
|
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||||
|
byte[] tag = tags.get(0);
|
||||||
|
StreamContext ctx = transportKeyManager.getStreamContext(txn, tag);
|
||||||
|
assertNotNull(ctx);
|
||||||
|
assertEquals(contactId, ctx.getContactId());
|
||||||
|
assertEquals(transportId, ctx.getTransportId());
|
||||||
|
assertEquals(tagKey, ctx.getTagKey());
|
||||||
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
|
assertEquals(0L, ctx.getStreamNumber());
|
||||||
|
// The keys are active so a stream context should be returned
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
ctx = transportKeyManager.getStreamContext(txn, contactId);
|
||||||
|
assertNotNull(ctx);
|
||||||
|
assertEquals(contactId, ctx.getContactId());
|
||||||
|
assertEquals(transportId, ctx.getTransportId());
|
||||||
|
assertEquals(tagKey, ctx.getTagKey());
|
||||||
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
|
assertEquals(0L, ctx.getStreamNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemovingUnboundKeys() throws Exception {
|
||||||
|
boolean alice = random.nextBoolean();
|
||||||
|
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// Remove the unbound keys
|
||||||
|
oneOf(db).removeTransportKeys(txn, transportId, keySetId);
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||||
|
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||||
|
maxLatency);
|
||||||
|
// The timestamp is at the start of rotation period 1000
|
||||||
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
|
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||||
|
masterKey, timestamp, alice));
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
transportKeyManager.removeKeys(txn, keySetId);
|
||||||
|
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectAddContactNoRotation(boolean alice,
|
||||||
|
TransportKeys transportKeys, Transaction txn) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||||
|
1000, alice, true);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Get the current time (the start of rotation period 1000)
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
|
// Encode the tags (3 sets)
|
||||||
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
|
exactly(3).of(transportCrypto).encodeTag(
|
||||||
|
with(any(byte[].class)), with(tagKey),
|
||||||
|
with(PROTOCOL_VERSION), with(i));
|
||||||
|
will(new EncodeTagAction());
|
||||||
|
}
|
||||||
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
|
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Save the keys
|
||||||
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
|
will(returnValue(keySetId));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectAddUnboundKeysNoRotation(boolean alice,
|
||||||
|
TransportKeys transportKeys, Transaction txn) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||||
|
1000, alice, false);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Get the current time (the start of rotation period 1000)
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
|
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Save the unbound keys
|
||||||
|
oneOf(db).addTransportKeys(txn, null, transportKeys);
|
||||||
|
will(returnValue(keySetId));
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportKeys createTransportKeys(long rotationPeriod,
|
private TransportKeys createTransportKeys(long rotationPeriod,
|
||||||
long streamCounter) {
|
long streamCounter, boolean active) {
|
||||||
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
|
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
|
||||||
rotationPeriod - 1);
|
rotationPeriod - 1);
|
||||||
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
|
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
|
||||||
@@ -417,7 +609,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
|
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
|
||||||
rotationPeriod + 1);
|
rotationPeriod + 1);
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
||||||
rotationPeriod, streamCounter);
|
rotationPeriod, streamCounter, active);
|
||||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,16 @@
|
|||||||
/>
|
/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="org.briarproject.briar.android.test.TestDataActivity"
|
||||||
|
android:label="Create test data"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.settings.SettingsActivity"
|
||||||
|
/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
android:name="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
||||||
android:label="@string/panic_setting"
|
android:label="@string/panic_setting"
|
||||||
@@ -374,7 +384,12 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.panic.ExitActivity"
|
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".android.logout.HideUiActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import javax.inject.Inject;
|
|||||||
import static android.app.Notification.DEFAULT_LIGHTS;
|
import static android.app.Notification.DEFAULT_LIGHTS;
|
||||||
import static android.app.Notification.DEFAULT_SOUND;
|
import static android.app.Notification.DEFAULT_SOUND;
|
||||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||||
|
import static android.app.Notification.VISIBILITY_SECRET;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
@@ -90,12 +91,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
||||||
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
||||||
|
|
||||||
// Channel IDs
|
|
||||||
private static final String CONTACT_CHANNEL_ID = "contacts";
|
|
||||||
private static final String GROUP_CHANNEL_ID = "groups";
|
|
||||||
private static final String FORUM_CHANNEL_ID = "forums";
|
|
||||||
private static final String BLOG_CHANNEL_ID = "blogs";
|
|
||||||
|
|
||||||
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -174,6 +169,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
NotificationChannel nc =
|
NotificationChannel nc =
|
||||||
new NotificationChannel(channelId, appContext.getString(name),
|
new NotificationChannel(channelId, appContext.getString(name),
|
||||||
IMPORTANCE_DEFAULT);
|
IMPORTANCE_DEFAULT);
|
||||||
|
nc.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||||
|
nc.enableVibration(true);
|
||||||
nc.enableLights(true);
|
nc.enableLights(true);
|
||||||
nc.setLightColor(
|
nc.setLightColor(
|
||||||
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import android.app.NotificationChannel;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -17,19 +20,26 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.logout.HideUiActivity;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||||
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||||
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
||||||
@@ -61,6 +71,9 @@ public class BriarService extends Service {
|
|||||||
private final AtomicBoolean created = new AtomicBoolean(false);
|
private final AtomicBoolean created = new AtomicBoolean(false);
|
||||||
private final Binder binder = new BriarBinder();
|
private final Binder binder = new BriarBinder();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BroadcastReceiver receiver = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected DatabaseConfig databaseConfig;
|
protected DatabaseConfig databaseConfig;
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@@ -140,6 +153,19 @@ public class BriarService extends Service {
|
|||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
// Register for device shutdown broadcasts
|
||||||
|
receiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
LOG.info("Device is shutting down");
|
||||||
|
shutdownFromBackground();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_SHUTDOWN);
|
||||||
|
filter.addAction("android.intent.action.QUICKBOOT_POWEROFF");
|
||||||
|
filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF");
|
||||||
|
registerReceiver(receiver, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStartupFailureNotification(StartResult result) {
|
private void showStartupFailureNotification(StartResult result) {
|
||||||
@@ -184,6 +210,7 @@ public class BriarService extends Service {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
LOG.info("Destroyed");
|
LOG.info("Destroyed");
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
|
if (receiver != null) unregisterReceiver(receiver);
|
||||||
// Stop the services in a background thread
|
// Stop the services in a background thread
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
if (started) lifecycleManager.stopServices();
|
if (started) lifecycleManager.stopServices();
|
||||||
@@ -194,7 +221,48 @@ public class BriarService extends Service {
|
|||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
super.onLowMemory();
|
super.onLowMemory();
|
||||||
LOG.warning("Memory is low");
|
LOG.warning("Memory is low");
|
||||||
// FIXME: Work out what to do about it
|
shutdownFromBackground();
|
||||||
|
showLowMemoryShutdownNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownFromBackground() {
|
||||||
|
// Stop the service
|
||||||
|
stopSelf();
|
||||||
|
// Hide the UI
|
||||||
|
Intent i = new Intent(this, HideUiActivity.class);
|
||||||
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||||
|
| FLAG_ACTIVITY_NO_ANIMATION
|
||||||
|
| FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(i);
|
||||||
|
// Wait for shutdown to complete, then exit
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
if (started) lifecycleManager.waitForShutdown();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.info("Interrupted while waiting for shutdown");
|
||||||
|
}
|
||||||
|
LOG.info("Exiting");
|
||||||
|
System.exit(0);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLowMemoryShutdownNotification() {
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||||
|
BriarService.this, FAILURE_CHANNEL_ID);
|
||||||
|
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||||
|
b.setContentTitle(getText(
|
||||||
|
R.string.low_memory_shutdown_notification_title));
|
||||||
|
b.setContentText(getText(
|
||||||
|
R.string.low_memory_shutdown_notification_text));
|
||||||
|
Intent i = new Intent(this, SplashScreenActivity.class);
|
||||||
|
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
||||||
|
b.setAutoCancel(true);
|
||||||
|
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
NotificationManager nm = (NotificationManager) o;
|
||||||
|
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import org.briarproject.briar.android.sharing.ShareForumFragment;
|
|||||||
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
|
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
|
import org.briarproject.briar.android.test.TestDataActivity;
|
||||||
|
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
|
|
||||||
@@ -147,6 +148,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(SettingsActivity activity);
|
void inject(SettingsActivity activity);
|
||||||
|
|
||||||
|
void inject(TestDataActivity activity);
|
||||||
|
|
||||||
void inject(ChangePasswordActivity activity);
|
void inject(ChangePasswordActivity activity);
|
||||||
|
|
||||||
void inject(IntroductionActivity activity);
|
void inject(IntroductionActivity activity);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import org.briarproject.briar.android.controller.BriarController;
|
|||||||
import org.briarproject.briar.android.controller.DbController;
|
import org.briarproject.briar.android.controller.DbController;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||||
import org.briarproject.briar.android.login.PasswordActivity;
|
import org.briarproject.briar.android.login.PasswordActivity;
|
||||||
import org.briarproject.briar.android.panic.ExitActivity;
|
import org.briarproject.briar.android.logout.ExitActivity;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|||||||
@@ -781,25 +781,18 @@ public class ConversationActivity extends BriarActivity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PromptStateChangeListener listener = new PromptStateChangeListener() {
|
PromptStateChangeListener listener = (prompt, state) -> {
|
||||||
@Override
|
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
|
||||||
public void onPromptStateChanged(
|
introductionOnboardingSeen();
|
||||||
MaterialTapTargetPrompt prompt, int state) {
|
}
|
||||||
if (state == STATE_DISMISSED ||
|
};
|
||||||
state == STATE_FINISHED) {
|
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
||||||
introductionOnboardingSeen();
|
R.style.OnboardingDialogTheme).setTarget(target)
|
||||||
}
|
.setPrimaryText(R.string.introduction_onboarding_title)
|
||||||
}
|
.setSecondaryText(R.string.introduction_onboarding_text)
|
||||||
|
.setIcon(R.drawable.ic_more_vert_accent)
|
||||||
};
|
.setPromptStateChangeListener(listener)
|
||||||
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
|
.show();
|
||||||
R.style.OnboardingDialogTheme).setTarget(target)
|
|
||||||
.setPrimaryText(R.string.introduction_onboarding_title)
|
|
||||||
.setSecondaryText(R.string.introduction_onboarding_text)
|
|
||||||
.setIcon(R.drawable.ic_more_vert_accent)
|
|
||||||
.setPromptStateChangeListener(listener)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,10 +858,12 @@ introductionOnboardingSeen();
|
|||||||
"Unknown Request Type");
|
"Unknown Request Type");
|
||||||
}
|
}
|
||||||
loadMessages();
|
loadMessages();
|
||||||
} catch (DbException | FormatException e) {
|
} catch (ProtocolStateException e) {
|
||||||
introductionResponseError();
|
// Action is no longer valid - reloading should solve the issue
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||||
LOG.log(WARNING, e.toString(), e);
|
} catch (DbException e) {
|
||||||
|
// TODO show an error message
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -898,12 +893,9 @@ introductionOnboardingSeen();
|
|||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void respondToIntroductionRequest(SessionId sessionId,
|
private void respondToIntroductionRequest(SessionId sessionId,
|
||||||
boolean accept, long time) throws DbException, FormatException {
|
boolean accept, long time) throws DbException {
|
||||||
if (accept) {
|
introductionManager.respondToIntroduction(contactId, sessionId, time,
|
||||||
introductionManager.acceptIntroduction(contactId, sessionId, time);
|
accept);
|
||||||
} else {
|
|
||||||
introductionManager.declineIntroduction(contactId, sessionId, time);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -921,18 +913,7 @@ introductionOnboardingSeen();
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void respondToGroupRequest(SessionId id, boolean accept)
|
private void respondToGroupRequest(SessionId id, boolean accept)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
try {
|
groupInvitationManager.respondToInvitation(contactId, id, accept);
|
||||||
groupInvitationManager.respondToInvitation(contactId, id, accept);
|
|
||||||
} catch (ProtocolStateException e) {
|
|
||||||
// this action is no longer possible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void introductionResponseError() {
|
|
||||||
runOnUiThreadUnlessDestroyed(() ->
|
|
||||||
Toast.makeText(ConversationActivity.this,
|
|
||||||
R.string.introduction_response_error,
|
|
||||||
Toast.LENGTH_SHORT).show());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFutureTask<String> getContactNameTask() {
|
private ListenableFutureTask<String> getContactNameTask() {
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ abstract class ConversationItem {
|
|||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_accepted_sent,
|
R.string.introduction_response_accepted_sent,
|
||||||
ir.getName());
|
ir.getName());
|
||||||
|
if (ir.isSuccessPossible()) {
|
||||||
|
text += "\n\n" + ctx.getString(
|
||||||
|
R.string.introduction_response_accepted_sent_info,
|
||||||
|
ir.getName());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_declined_sent,
|
R.string.introduction_response_declined_sent,
|
||||||
|
|||||||
@@ -52,19 +52,15 @@ class ForumListAdapter
|
|||||||
// Post Count
|
// Post Count
|
||||||
int postCount = item.getPostCount();
|
int postCount = item.getPostCount();
|
||||||
if (postCount > 0) {
|
if (postCount > 0) {
|
||||||
ui.avatar.setProblem(false);
|
|
||||||
ui.postCount.setText(ctx.getResources()
|
ui.postCount.setText(ctx.getResources()
|
||||||
.getQuantityString(R.plurals.posts, postCount,
|
.getQuantityString(R.plurals.posts, postCount,
|
||||||
postCount));
|
postCount));
|
||||||
ui.postCount.setTextColor(
|
ui.postCount.setTextColor(
|
||||||
ContextCompat
|
ContextCompat.getColor(ctx, R.color.briar_text_secondary));
|
||||||
.getColor(ctx, R.color.briar_text_secondary));
|
|
||||||
} else {
|
} else {
|
||||||
ui.avatar.setProblem(true);
|
|
||||||
ui.postCount.setText(ctx.getString(R.string.no_posts));
|
ui.postCount.setText(ctx.getString(R.string.no_posts));
|
||||||
ui.postCount.setTextColor(
|
ui.postCount.setTextColor(
|
||||||
ContextCompat
|
ContextCompat.getColor(ctx, R.color.briar_text_tertiary));
|
||||||
.getColor(ctx, R.color.briar_text_tertiary));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Date
|
// Date
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.introduction;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -11,7 +12,6 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
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;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -33,9 +33,10 @@ import im.delight.android.identicons.IdenticonDrawable;
|
|||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
|
||||||
|
|
||||||
public class IntroductionMessageFragment extends BaseFragment
|
public class IntroductionMessageFragment extends BaseFragment
|
||||||
implements TextInputListener {
|
implements TextInputListener {
|
||||||
@@ -125,14 +126,15 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
new ContactId(contactId1));
|
new ContactId(contactId1));
|
||||||
Contact c2 = contactManager.getContact(
|
Contact c2 = contactManager.getContact(
|
||||||
new ContactId(contactId2));
|
new ContactId(contactId2));
|
||||||
setUpViews(c1, c2);
|
boolean possible = introductionManager.canIntroduce(c1, c2);
|
||||||
|
setUpViews(c1, c2, possible);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpViews(Contact c1, Contact c2) {
|
private void setUpViews(Contact c1, Contact c2, boolean possible) {
|
||||||
introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
|
introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
|
||||||
contact1 = c1;
|
contact1 = c1;
|
||||||
contact2 = c2;
|
contact2 = c2;
|
||||||
@@ -147,13 +149,22 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
ui.contactName1.setText(c1.getAuthor().getName());
|
ui.contactName1.setText(c1.getAuthor().getName());
|
||||||
ui.contactName2.setText(c2.getAuthor().getName());
|
ui.contactName2.setText(c2.getAuthor().getName());
|
||||||
|
|
||||||
// set button action
|
// hide progress bar
|
||||||
ui.message.setListener(IntroductionMessageFragment.this);
|
|
||||||
|
|
||||||
// hide progress bar and show views
|
|
||||||
ui.progressBar.setVisibility(GONE);
|
ui.progressBar.setVisibility(GONE);
|
||||||
ui.message.setSendButtonEnabled(true);
|
|
||||||
ui.message.showSoftKeyboard();
|
if (possible) {
|
||||||
|
// set button action
|
||||||
|
ui.message.setListener(IntroductionMessageFragment.this);
|
||||||
|
|
||||||
|
// show views
|
||||||
|
ui.notPossible.setVisibility(GONE);
|
||||||
|
ui.message.setVisibility(VISIBLE);
|
||||||
|
ui.message.setSendButtonEnabled(true);
|
||||||
|
ui.message.showSoftKeyboard();
|
||||||
|
} else {
|
||||||
|
ui.notPossible.setVisibility(VISIBLE);
|
||||||
|
ui.message.setVisibility(GONE);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +186,8 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
ui.message.setSendButtonEnabled(false);
|
ui.message.setSendButtonEnabled(false);
|
||||||
|
|
||||||
String msg = ui.message.getText().toString();
|
String msg = ui.message.getText().toString();
|
||||||
msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH);
|
if (msg.equals("")) msg = null;
|
||||||
|
else msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH);
|
||||||
makeIntroduction(contact1, contact2, msg);
|
makeIntroduction(contact1, contact2, msg);
|
||||||
|
|
||||||
// don't wait for the introduction to be made before finishing activity
|
// don't wait for the introduction to be made before finishing activity
|
||||||
@@ -184,13 +196,14 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
introductionActivity.supportFinishAfterTransition();
|
introductionActivity.supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeIntroduction(Contact c1, Contact c2, String msg) {
|
private void makeIntroduction(Contact c1, Contact c2,
|
||||||
|
@Nullable String msg) {
|
||||||
introductionActivity.runOnDbThread(() -> {
|
introductionActivity.runOnDbThread(() -> {
|
||||||
// actually make the introduction
|
// actually make the introduction
|
||||||
try {
|
try {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
|
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
|
||||||
} catch (DbException | FormatException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
introductionError();
|
introductionError();
|
||||||
}
|
}
|
||||||
@@ -208,6 +221,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
private final CircleImageView avatar1, avatar2;
|
private final CircleImageView avatar1, avatar2;
|
||||||
private final TextView contactName1, contactName2;
|
private final TextView contactName1, contactName2;
|
||||||
|
private final TextView notPossible;
|
||||||
private final TextInputView message;
|
private final TextInputView message;
|
||||||
|
|
||||||
private ViewHolder(View v) {
|
private ViewHolder(View v) {
|
||||||
@@ -216,6 +230,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
avatar2 = v.findViewById(R.id.avatarContact2);
|
avatar2 = v.findViewById(R.id.avatarContact2);
|
||||||
contactName1 = v.findViewById(R.id.nameContact1);
|
contactName1 = v.findViewById(R.id.nameContact1);
|
||||||
contactName2 = v.findViewById(R.id.nameContact2);
|
contactName2 = v.findViewById(R.id.nameContact2);
|
||||||
|
notPossible = v.findViewById(R.id.introductionNotPossibleView);
|
||||||
message = v.findViewById(R.id.introductionMessageView);
|
message = v.findViewById(R.id.introductionMessageView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -52,6 +54,8 @@ import javax.inject.Provider;
|
|||||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
|
import static android.widget.LinearLayout.HORIZONTAL;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
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;
|
||||||
@@ -84,6 +88,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
private ImageView qrCode;
|
private ImageView qrCode;
|
||||||
private TextView mainProgressTitle;
|
private TextView mainProgressTitle;
|
||||||
private ViewGroup mainProgressContainer;
|
private ViewGroup mainProgressContainer;
|
||||||
|
private boolean fullscreen = false;
|
||||||
|
|
||||||
private boolean gotRemotePayload;
|
private boolean gotRemotePayload;
|
||||||
private volatile boolean gotLocalPayload;
|
private volatile boolean gotLocalPayload;
|
||||||
@@ -128,6 +133,34 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
qrCode = view.findViewById(R.id.qr_code);
|
qrCode = view.findViewById(R.id.qr_code);
|
||||||
mainProgressTitle = view.findViewById(R.id.title_progress_bar);
|
mainProgressTitle = view.findViewById(R.id.title_progress_bar);
|
||||||
mainProgressContainer = view.findViewById(R.id.container_progress);
|
mainProgressContainer = view.findViewById(R.id.container_progress);
|
||||||
|
ImageView fullscreenButton = view.findViewById(R.id.fullscreen_button);
|
||||||
|
fullscreenButton.setOnClickListener(v -> {
|
||||||
|
View qrCodeContainer = view.findViewById(R.id.qr_code_container);
|
||||||
|
LinearLayout cameraOverlay = view.findViewById(R.id.camera_overlay);
|
||||||
|
LayoutParams statusParams, qrCodeParams;
|
||||||
|
if (fullscreen) {
|
||||||
|
// Shrink the QR code container to fill half its parent
|
||||||
|
if (cameraOverlay.getOrientation() == HORIZONTAL) {
|
||||||
|
statusParams = new LayoutParams(0, MATCH_PARENT, 1f);
|
||||||
|
qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f);
|
||||||
|
} else {
|
||||||
|
statusParams = new LayoutParams(MATCH_PARENT, 0, 1f);
|
||||||
|
qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f);
|
||||||
|
}
|
||||||
|
fullscreenButton.setImageResource(
|
||||||
|
R.drawable.ic_fullscreen_black_48dp);
|
||||||
|
} else {
|
||||||
|
// Grow the QR code container to fill its parent
|
||||||
|
statusParams = new LayoutParams(0, 0, 0f);
|
||||||
|
qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
|
||||||
|
fullscreenButton.setImageResource(
|
||||||
|
R.drawable.ic_fullscreen_exit_black_48dp);
|
||||||
|
}
|
||||||
|
statusView.setLayoutParams(statusParams);
|
||||||
|
qrCodeContainer.setLayoutParams(qrCodeParams);
|
||||||
|
cameraOverlay.invalidate();
|
||||||
|
fullscreen = !fullscreen;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -284,6 +317,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
new AsyncTask<Void, Void, Bitmap>() {
|
new AsyncTask<Void, Void, Bitmap>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
protected Bitmap doInBackground(Void... params) {
|
protected Bitmap doInBackground(Void... params) {
|
||||||
byte[] payloadBytes = payloadEncoder.encode(payload);
|
byte[] payloadBytes = payloadEncoder.encode(payload);
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.panic;
|
package org.briarproject.briar.android.logout;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.briar.android.logout;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
|
||||||
|
public class HideUiActivity extends BaseActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.fragment;
|
package org.briarproject.briar.android.logout;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -7,15 +7,17 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class SignOutFragment extends BaseFragment {
|
public class SignOutFragment extends BaseFragment {
|
||||||
|
|
||||||
private static final String TAG = SignOutFragment.class.getName();
|
public static final String TAG = SignOutFragment.class.getName();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(@Nonnull LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_sign_out, container, false);
|
return inflater.inflate(R.layout.fragment_sign_out, container, false);
|
||||||
@@ -30,5 +32,4 @@ public class SignOutFragment extends BaseFragment {
|
|||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
// no need to inject
|
// no need to inject
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.design.widget.NavigationView;
|
import android.support.design.widget.NavigationView;
|
||||||
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
|
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
@@ -22,6 +23,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||||
@@ -35,7 +37,7 @@ import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
|||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||||
import org.briarproject.briar.android.fragment.SignOutFragment;
|
import org.briarproject.briar.android.logout.SignOutFragment;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning;
|
import org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
|
import org.briarproject.briar.android.privategroup.list.GroupListFragment;
|
||||||
import org.briarproject.briar.android.settings.SettingsActivity;
|
import org.briarproject.briar.android.settings.SettingsActivity;
|
||||||
@@ -51,6 +53,7 @@ import static android.support.v4.view.GravityCompat.START;
|
|||||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
||||||
@@ -73,6 +76,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NavDrawerController controller;
|
NavDrawerController controller;
|
||||||
|
@Inject
|
||||||
|
LifecycleManager lifecycleManager;
|
||||||
|
|
||||||
private DrawerLayout drawerLayout;
|
private DrawerLayout drawerLayout;
|
||||||
private NavigationView navigation;
|
private NavigationView navigation;
|
||||||
@@ -128,7 +133,9 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
initializeTransports(getLayoutInflater());
|
initializeTransports(getLayoutInflater());
|
||||||
transportsView.setAdapter(transportsAdapter);
|
transportsView.setAdapter(transportsAdapter);
|
||||||
|
|
||||||
if (state == null) {
|
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
|
||||||
|
showSignOutFragment();
|
||||||
|
} else if (state == null) {
|
||||||
startFragment(ContactListFragment.newInstance(),
|
startFragment(ContactListFragment.newInstance(),
|
||||||
R.id.nav_btn_contacts);
|
R.id.nav_btn_contacts);
|
||||||
}
|
}
|
||||||
@@ -212,19 +219,23 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (drawerLayout.isDrawerOpen(START)) {
|
if (drawerLayout.isDrawerOpen(START)) {
|
||||||
drawerLayout.closeDrawer(START);
|
drawerLayout.closeDrawer(START);
|
||||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
} else {
|
||||||
getSupportFragmentManager()
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
|
||||||
|
finish();
|
||||||
|
} else if (fm.getBackStackEntryCount() == 0
|
||||||
|
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||||
/*
|
/*
|
||||||
* This makes sure that the first fragment (ContactListFragment) the
|
* This makes sure that the first fragment (ContactListFragment) the
|
||||||
* user sees is the same as the last fragment the user sees before
|
* user sees is the same as the last fragment the user sees before
|
||||||
* exiting. This models the typical Google navigation behaviour such
|
* exiting. This models the typical Google navigation behaviour such
|
||||||
* as in Gmail/Inbox.
|
* as in Gmail/Inbox.
|
||||||
*/
|
*/
|
||||||
startFragment(ContactListFragment.newInstance(),
|
startFragment(ContactListFragment.newInstance(),
|
||||||
R.id.nav_btn_contacts);
|
R.id.nav_btn_contacts);
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,10 +251,15 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
drawerToggle.onConfigurationChanged(newConfig);
|
drawerToggle.onConfigurationChanged(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void signOut() {
|
private void showSignOutFragment() {
|
||||||
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
||||||
startFragment(new SignOutFragment());
|
startFragment(new SignOutFragment());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signOut() {
|
||||||
|
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
||||||
signOut(false);
|
signOut(false);
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFragment(BaseFragment fragment, int itemId) {
|
private void startFragment(BaseFragment fragment, int itemId) {
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (group.isEmpty()) {
|
if (group.isEmpty()) {
|
||||||
postCount.setVisibility(GONE);
|
postCount.setVisibility(GONE);
|
||||||
date.setVisibility(GONE);
|
date.setVisibility(GONE);
|
||||||
avatar.setProblem(true);
|
status.setText(ctx.getString(R.string.groups_group_is_empty));
|
||||||
status
|
|
||||||
.setText(ctx.getString(R.string.groups_group_is_empty));
|
|
||||||
status.setVisibility(VISIBLE);
|
status.setVisibility(VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
// Message Count
|
// Message Count
|
||||||
@@ -91,7 +89,6 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
long lastUpdate = group.getTimestamp();
|
long lastUpdate = group.getTimestamp();
|
||||||
date.setText(UiUtils.formatDate(ctx, lastUpdate));
|
date.setText(UiUtils.formatDate(ctx, lastUpdate));
|
||||||
date.setVisibility(VISIBLE);
|
date.setVisibility(VISIBLE);
|
||||||
avatar.setProblem(false);
|
|
||||||
status.setVisibility(GONE);
|
status.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
remove.setVisibility(GONE);
|
remove.setVisibility(GONE);
|
||||||
|
|||||||
@@ -173,7 +173,12 @@ public class BriarReportPrimer implements ReportPrimer {
|
|||||||
WifiInfo wifiInfo = wm.getConnectionInfo();
|
WifiInfo wifiInfo = wm.getConnectionInfo();
|
||||||
if (wifiInfo != null) {
|
if (wifiInfo != null) {
|
||||||
int ip = wifiInfo.getIpAddress(); // Nice API, Google
|
int ip = wifiInfo.getIpAddress(); // Nice API, Google
|
||||||
customData.put("Wi-Fi address", StringUtils.ipToString(ip));
|
int ip1 = ip & 0xFF;
|
||||||
|
int ip2 = (ip >> 8) & 0xFF;
|
||||||
|
int ip3 = (ip >> 16) & 0xFF;
|
||||||
|
int ip4 = (ip >> 24) & 0xFF;
|
||||||
|
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
|
||||||
|
customData.put("Wi-Fi address", address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package org.briarproject.briar.android.settings;
|
package org.briarproject.briar.android.settings;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.media.Ringtone;
|
import android.media.Ringtone;
|
||||||
import android.media.RingtoneManager;
|
import android.media.RingtoneManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.v7.preference.CheckBoxPreference;
|
import android.support.v7.preference.CheckBoxPreference;
|
||||||
import android.support.v7.preference.ListPreference;
|
import android.support.v7.preference.ListPreference;
|
||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
@@ -29,7 +30,6 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
|
|||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.UserFeedback;
|
import org.briarproject.briar.android.util.UserFeedback;
|
||||||
import org.briarproject.briar.api.test.TestDataCreator;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -44,6 +44,10 @@ import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT;
|
|||||||
import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE;
|
import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE;
|
||||||
import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE;
|
import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE;
|
||||||
import static android.media.RingtoneManager.TYPE_NOTIFICATION;
|
import static android.media.RingtoneManager.TYPE_NOTIFICATION;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS;
|
||||||
|
import static android.provider.Settings.EXTRA_APP_PACKAGE;
|
||||||
|
import static android.provider.Settings.EXTRA_CHANNEL_ID;
|
||||||
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
@@ -53,6 +57,10 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
|||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
|
||||||
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
|
||||||
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID;
|
||||||
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID;
|
||||||
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_CHANNEL_ID;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
|
||||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
|
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
|
||||||
@@ -96,8 +104,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidExecutor androidExecutor;
|
AndroidExecutor androidExecutor;
|
||||||
@Inject
|
|
||||||
TestDataCreator testDataCreator;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
@@ -132,33 +138,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||||
torNetwork.setOnPreferenceChangeListener(this);
|
torNetwork.setOnPreferenceChangeListener(this);
|
||||||
notifyPrivateMessages.setOnPreferenceChangeListener(this);
|
if (SDK_INT >= 21) {
|
||||||
notifyGroupMessages.setOnPreferenceChangeListener(this);
|
|
||||||
notifyForumPosts.setOnPreferenceChangeListener(this);
|
|
||||||
notifyBlogPosts.setOnPreferenceChangeListener(this);
|
|
||||||
notifyVibration.setOnPreferenceChangeListener(this);
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
notifyLockscreen.setVisible(true);
|
notifyLockscreen.setVisible(true);
|
||||||
notifyLockscreen.setOnPreferenceChangeListener(this);
|
notifyLockscreen.setOnPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
notifySound.setOnPreferenceClickListener(preference -> {
|
|
||||||
String title = getString(R.string.choose_ringtone_title);
|
|
||||||
Intent i = new Intent(ACTION_RINGTONE_PICKER);
|
|
||||||
i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
|
|
||||||
i.putExtra(EXTRA_RINGTONE_TITLE, title);
|
|
||||||
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI, DEFAULT_NOTIFICATION_URI);
|
|
||||||
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
|
|
||||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
|
||||||
Uri uri;
|
|
||||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
|
||||||
if (StringUtils.isNullOrEmpty(ringtoneUri))
|
|
||||||
uri = DEFAULT_NOTIFICATION_URI;
|
|
||||||
else uri = Uri.parse(ringtoneUri);
|
|
||||||
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
|
|
||||||
}
|
|
||||||
startActivityForResult(i, REQUEST_RINGTONE);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("pref_key_send_feedback").setOnPreferenceClickListener(
|
findPreference("pref_key_send_feedback").setOnPreferenceClickListener(
|
||||||
preference -> {
|
preference -> {
|
||||||
@@ -167,14 +150,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
});
|
});
|
||||||
|
|
||||||
Preference testData = findPreference("pref_key_test_data");
|
Preference testData = findPreference("pref_key_test_data");
|
||||||
if (IS_DEBUG_BUILD) {
|
if (!IS_DEBUG_BUILD) {
|
||||||
testData.setOnPreferenceClickListener(preference -> {
|
|
||||||
LOG.info("Creating test data");
|
|
||||||
testDataCreator.createTestData();
|
|
||||||
getActivity().finish();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
testData.setVisible(false);
|
testData.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,36 +196,54 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
enableBluetooth.setValue(Boolean.toString(btSetting));
|
enableBluetooth.setValue(Boolean.toString(btSetting));
|
||||||
torNetwork.setValue(Integer.toString(torSetting));
|
torNetwork.setValue(Integer.toString(torSetting));
|
||||||
|
|
||||||
notifyPrivateMessages.setChecked(settings.getBoolean(
|
if (SDK_INT < 26) {
|
||||||
PREF_NOTIFY_PRIVATE, true));
|
notifyPrivateMessages.setChecked(settings.getBoolean(
|
||||||
|
PREF_NOTIFY_PRIVATE, true));
|
||||||
notifyGroupMessages.setChecked(settings.getBoolean(
|
notifyGroupMessages.setChecked(settings.getBoolean(
|
||||||
PREF_NOTIFY_GROUP, true));
|
PREF_NOTIFY_GROUP, true));
|
||||||
|
notifyForumPosts.setChecked(settings.getBoolean(
|
||||||
notifyForumPosts.setChecked(settings.getBoolean(
|
PREF_NOTIFY_FORUM, true));
|
||||||
PREF_NOTIFY_FORUM, true));
|
notifyBlogPosts.setChecked(settings.getBoolean(
|
||||||
|
PREF_NOTIFY_BLOG, true));
|
||||||
notifyBlogPosts.setChecked(settings.getBoolean(
|
notifyVibration.setChecked(settings.getBoolean(
|
||||||
PREF_NOTIFY_BLOG, true));
|
PREF_NOTIFY_VIBRATION, true));
|
||||||
|
notifyPrivateMessages.setOnPreferenceChangeListener(this);
|
||||||
notifyVibration.setChecked(settings.getBoolean(
|
notifyGroupMessages.setOnPreferenceChangeListener(this);
|
||||||
PREF_NOTIFY_VIBRATION, true));
|
notifyForumPosts.setOnPreferenceChangeListener(this);
|
||||||
|
notifyBlogPosts.setOnPreferenceChangeListener(this);
|
||||||
notifyLockscreen.setChecked(settings.getBoolean(
|
notifyVibration.setOnPreferenceChangeListener(this);
|
||||||
PREF_NOTIFY_LOCK_SCREEN, false));
|
notifyLockscreen.setChecked(settings.getBoolean(
|
||||||
|
PREF_NOTIFY_LOCK_SCREEN, false));
|
||||||
String text;
|
notifySound.setOnPreferenceClickListener(
|
||||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
pref -> onNotificationSoundClicked());
|
||||||
String ringtoneName = settings.get(PREF_NOTIFY_RINGTONE_NAME);
|
String text;
|
||||||
if (StringUtils.isNullOrEmpty(ringtoneName)) {
|
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||||
text = getString(R.string.notify_sound_setting_default);
|
String ringtoneName =
|
||||||
|
settings.get(PREF_NOTIFY_RINGTONE_NAME);
|
||||||
|
if (StringUtils.isNullOrEmpty(ringtoneName)) {
|
||||||
|
text = getString(R.string.notify_sound_setting_default);
|
||||||
|
} else {
|
||||||
|
text = ringtoneName;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = ringtoneName;
|
text = getString(R.string.notify_sound_setting_disabled);
|
||||||
}
|
}
|
||||||
|
notifySound.setSummary(text);
|
||||||
} else {
|
} else {
|
||||||
text = getString(R.string.notify_sound_setting_disabled);
|
setupNotificationPreference(notifyPrivateMessages,
|
||||||
|
CONTACT_CHANNEL_ID,
|
||||||
|
R.string.notify_private_messages_setting_summary_26);
|
||||||
|
setupNotificationPreference(notifyGroupMessages,
|
||||||
|
GROUP_CHANNEL_ID,
|
||||||
|
R.string.notify_group_messages_setting_summary_26);
|
||||||
|
setupNotificationPreference(notifyForumPosts, FORUM_CHANNEL_ID,
|
||||||
|
R.string.notify_forum_posts_setting_summary_26);
|
||||||
|
setupNotificationPreference(notifyBlogPosts, BLOG_CHANNEL_ID,
|
||||||
|
R.string.notify_blog_posts_setting_summary_26);
|
||||||
|
notifyVibration.setVisible(false);
|
||||||
|
notifyLockscreen.setVisible(false);
|
||||||
|
notifySound.setVisible(false);
|
||||||
}
|
}
|
||||||
notifySound.setSummary(text);
|
|
||||||
setSettingsEnabled(true);
|
setSettingsEnabled(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -266,6 +260,41 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
notifySound.setEnabled(enabled);
|
notifySound.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
private void setupNotificationPreference(CheckBoxPreference pref,
|
||||||
|
String channelId, @StringRes int summary) {
|
||||||
|
pref.setWidgetLayoutResource(0);
|
||||||
|
pref.setSummary(summary);
|
||||||
|
pref.setOnPreferenceClickListener(clickedPref -> {
|
||||||
|
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
|
.putExtra(EXTRA_APP_PACKAGE, getContext().getPackageName())
|
||||||
|
.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onNotificationSoundClicked() {
|
||||||
|
String title = getString(R.string.choose_ringtone_title);
|
||||||
|
Intent i = new Intent(ACTION_RINGTONE_PICKER);
|
||||||
|
i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
|
||||||
|
i.putExtra(EXTRA_RINGTONE_TITLE, title);
|
||||||
|
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI,
|
||||||
|
DEFAULT_NOTIFICATION_URI);
|
||||||
|
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
|
||||||
|
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||||
|
Uri uri;
|
||||||
|
String ringtoneUri =
|
||||||
|
settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||||
|
if (StringUtils.isNullOrEmpty(ringtoneUri))
|
||||||
|
uri = DEFAULT_NOTIFICATION_URI;
|
||||||
|
else uri = Uri.parse(ringtoneUri);
|
||||||
|
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
|
||||||
|
}
|
||||||
|
startActivityForResult(i, REQUEST_RINGTONE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void triggerFeedback() {
|
private void triggerFeedback() {
|
||||||
androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter()
|
androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter()
|
||||||
.handleException(new UserFeedback(), false));
|
.handleException(new UserFeedback(), false));
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package org.briarproject.briar.android.splash;
|
package org.briarproject.briar.android.splash;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_VIEW;
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||||
|
|
||||||
public class ExpiredActivity extends Activity {
|
public class ExpiredActivity extends AppCompatActivity
|
||||||
|
implements OnClickListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(Bundle state) {
|
||||||
@@ -17,5 +23,13 @@ public class ExpiredActivity extends Activity {
|
|||||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||||
|
|
||||||
setContentView(R.layout.activity_expired);
|
setContentView(R.layout.activity_expired);
|
||||||
|
findViewById(R.id.download_briar_button).setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Uri uri = Uri.parse("https://briarproject.org/download.html");
|
||||||
|
startActivity(new Intent(ACTION_VIEW, uri));
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package org.briarproject.briar.android.test;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||||
|
import org.briarproject.briar.api.test.TestDataCreator;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
|
|
||||||
|
public class TestDataActivity extends BriarActivity {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TestDataCreator testDataCreator;
|
||||||
|
|
||||||
|
private TextView[] textViews = new TextView[5];
|
||||||
|
private SeekBar[] seekBars = new SeekBar[5];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle) {
|
||||||
|
super.onCreate(bundle);
|
||||||
|
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setHomeButtonEnabled(true);
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_test_data);
|
||||||
|
textViews[0] = findViewById(R.id.textViewContactsSb);
|
||||||
|
textViews[1] = findViewById(R.id.textViewMessagesSb);
|
||||||
|
textViews[2] = findViewById(R.id.TextViewBlogPostsSb);
|
||||||
|
textViews[3] = findViewById(R.id.TextViewForumsSb);
|
||||||
|
textViews[4] = findViewById(R.id.TextViewForumMessagesSb);
|
||||||
|
seekBars[0] = findViewById(R.id.seekBarContacts);
|
||||||
|
seekBars[1] = findViewById(R.id.seekBarMessages);
|
||||||
|
seekBars[2] = findViewById(R.id.seekBarBlogPosts);
|
||||||
|
seekBars[3] = findViewById(R.id.seekBarForums);
|
||||||
|
seekBars[4] = findViewById(R.id.seekBarForumMessages);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
final TextView textView = textViews[i];
|
||||||
|
seekBars[i].setOnSeekBarChangeListener(
|
||||||
|
new OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar,
|
||||||
|
int progress, boolean fromUser) {
|
||||||
|
textView.setText(String.valueOf(progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById(R.id.buttonCreateTestData).setOnClickListener(
|
||||||
|
v -> createTestData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTestData() {
|
||||||
|
testDataCreator.createTestData(seekBars[0].getProgress(),
|
||||||
|
seekBars[1].getProgress(), seekBars[2].getProgress(),
|
||||||
|
seekBars[3].getProgress(), seekBars[4].getProgress());
|
||||||
|
Intent intent = new Intent(this, NavDrawerActivity.class);
|
||||||
|
intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import android.graphics.Color;
|
|||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.widget.AppCompatTextView;
|
import android.support.v7.widget.AppCompatTextView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -26,7 +25,6 @@ public class TextAvatarView extends FrameLayout {
|
|||||||
private final AppCompatTextView character;
|
private final AppCompatTextView character;
|
||||||
private final CircleImageView background;
|
private final CircleImageView background;
|
||||||
private final TextView badge;
|
private final TextView badge;
|
||||||
private int unreadCount;
|
|
||||||
|
|
||||||
public TextAvatarView(Context context, @Nullable AttributeSet attrs) {
|
public TextAvatarView(Context context, @Nullable AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@@ -49,30 +47,14 @@ public class TextAvatarView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUnreadCount(int count) {
|
public void setUnreadCount(int count) {
|
||||||
unreadCount = count;
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
badge.setBackgroundResource(R.drawable.bubble);
|
|
||||||
badge.setText(String.valueOf(count));
|
badge.setText(String.valueOf(count));
|
||||||
badge.setTextColor(ContextCompat.getColor(getContext(),
|
|
||||||
R.color.briar_text_primary_inverse));
|
|
||||||
badge.setVisibility(VISIBLE);
|
badge.setVisibility(VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
badge.setVisibility(INVISIBLE);
|
badge.setVisibility(INVISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProblem(boolean problem) {
|
|
||||||
if (problem) {
|
|
||||||
badge.setBackgroundResource(R.drawable.bubble_problem);
|
|
||||||
badge.setText("!");
|
|
||||||
badge.setTextColor(ContextCompat
|
|
||||||
.getColor(getContext(), R.color.briar_primary));
|
|
||||||
badge.setVisibility(VISIBLE);
|
|
||||||
} else {
|
|
||||||
setUnreadCount(unreadCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBackgroundBytes(byte[] bytes) {
|
public void setBackgroundBytes(byte[] bytes) {
|
||||||
int r = getByte(bytes, 0) * 3 / 4 + 96;
|
int r = getByte(bytes, 0) * 3 / 4 + 96;
|
||||||
int g = getByte(bytes, 1) * 3 / 4 + 96;
|
int g = getByte(bytes, 1) * 3 / 4 + 96;
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ public interface AndroidNotificationManager {
|
|||||||
String PREF_NOTIFY_VIBRATION = "notifyVibration";
|
String PREF_NOTIFY_VIBRATION = "notifyVibration";
|
||||||
String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen";
|
String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen";
|
||||||
|
|
||||||
|
// Channel IDs
|
||||||
|
String CONTACT_CHANNEL_ID = "contacts";
|
||||||
|
String GROUP_CHANNEL_ID = "groups";
|
||||||
|
String FORUM_CHANNEL_ID = "forums";
|
||||||
|
String BLOG_CHANNEL_ID = "blogs";
|
||||||
|
|
||||||
// Content URIs for pending intents
|
// Content URIs for pending intents
|
||||||
String CONTACT_URI = "content://org.briarproject.briar/contact";
|
String CONTACT_URI = "content://org.briarproject.briar/contact";
|
||||||
String GROUP_URI = "content://org.briarproject.briar/group";
|
String GROUP_URI = "content://org.briarproject.briar/group";
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<corners
|
|
||||||
android:radius="@dimen/unread_bubble_size"/>
|
|
||||||
|
|
||||||
<padding
|
|
||||||
android:left="@dimen/unread_bubble_padding_horizontal"
|
|
||||||
android:right="@dimen/unread_bubble_padding_horizontal"/>
|
|
||||||
|
|
||||||
<solid
|
|
||||||
android:color="@color/briar_gold"/>
|
|
||||||
|
|
||||||
<stroke
|
|
||||||
android:color="@color/briar_primary"
|
|
||||||
android:width="@dimen/avatar_border_width"/>
|
|
||||||
|
|
||||||
</shape>
|
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="48dp" android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="48dp" android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"/>
|
||||||
|
</vector>
|
||||||
@@ -15,39 +15,35 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:weightSum="2">
|
android:baselineAligned="false">
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/status_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/background_light"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/margin_medium"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
android:id="@+id/status_container"
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/connect_status"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/background_light"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:paddingTop="@dimen/margin_large"
|
||||||
android:padding="@dimen/margin_medium"
|
tools:text="Connection failed"/>
|
||||||
android:visibility="invisible">
|
</LinearLayout>
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/connect_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingTop="@dimen/margin_large"
|
|
||||||
tools:text="Connection failed"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/qr_code_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -59,12 +55,31 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"/>
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
<ImageView
|
<RelativeLayout
|
||||||
android:id="@+id/qr_code"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:layout_gravity="center"/>
|
<ImageView
|
||||||
|
android:id="@+id/qr_code"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:contentDescription="@string/qr_code"
|
||||||
|
android:scaleType="fitCenter"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/fullscreen_button"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:src="@drawable/ic_fullscreen_black_48dp"
|
||||||
|
android:alpha="0.54"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_small"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:contentDescription="@string/show_qr_code_fullscreen"/>
|
||||||
|
</RelativeLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -92,5 +107,4 @@
|
|||||||
android:paddingTop="@dimen/margin_large"
|
android:paddingTop="@dimen/margin_large"
|
||||||
tools:text="@string/waiting_for_contact_to_scan"/>
|
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,9 +1,47 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView
|
<ScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:orientation="vertical">
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/expiry_date_reached"
|
<LinearLayout
|
||||||
android:textSize="@dimen/text_size_large"/>
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_large"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/expiry_date_reached"
|
||||||
|
android:textSize="@dimen/text_size_large"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/download_briar"
|
||||||
|
android:textSize="@dimen/text_size_large"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/create_new_account"
|
||||||
|
android:textSize="@dimen/text_size_large"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/download_briar_button"
|
||||||
|
style="@style/BriarButton.Default"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:text="@string/download_briar_button"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
170
briar-android/src/main/res/layout/activity_test_data.xml
Normal file
170
briar-android/src/main/res/layout/activity_test_data.xml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewContacts"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Number of contacts"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarContacts"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="50"
|
||||||
|
android:progress="20"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/textViewContactsSb"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewContacts"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewContactsSb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="2"
|
||||||
|
android:text="20"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/seekBarContacts"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewMessages"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Number of messages per contact"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/seekBarContacts"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarMessages"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="50"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:progress="15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/textViewMessagesSb"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewMessages"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewMessagesSb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="2"
|
||||||
|
android:text="20"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/seekBarMessages"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewBlogPosts"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Number of blog posts"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/seekBarMessages"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarBlogPosts"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="50"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:progress="30"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/TextViewBlogPostsSb"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewBlogPosts"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/TextViewBlogPostsSb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="2"
|
||||||
|
android:text="20"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/seekBarBlogPosts"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewForums"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Number of forums"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/seekBarBlogPosts"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarForums"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="10"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:progress="3"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/TextViewForumsSb"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewForums"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/TextViewForumsSb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="2"
|
||||||
|
android:text="20"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/seekBarForums"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewForumMessages"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Number of forum messages"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/seekBarForums"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarForumMessages"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="50"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:progress="30"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/TextViewForumMessagesSb"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewForumMessages"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/TextViewForumMessagesSb"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="2"
|
||||||
|
android:text="20"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/seekBarForumMessages"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/buttonCreateTestData"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:text="Create test data"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/seekBarForumMessages"/>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
@@ -15,39 +15,35 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:weightSum="2">
|
android:baselineAligned="false">
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/status_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/background_light"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/margin_medium"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
android:id="@+id/status_container"
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/connect_status"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/background_light"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:paddingTop="@dimen/margin_large"
|
||||||
android:padding="@dimen/margin_medium"
|
tools:text="Connection failed"/>
|
||||||
android:visibility="invisible">
|
</LinearLayout>
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/connect_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingTop="@dimen/margin_large"
|
|
||||||
tools:text="Connection failed"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/qr_code_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -59,12 +55,31 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"/>
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
<ImageView
|
<RelativeLayout
|
||||||
android:id="@+id/qr_code"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:layout_gravity="center"/>
|
<ImageView
|
||||||
|
android:id="@+id/qr_code"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:contentDescription="@string/qr_code"
|
||||||
|
android:scaleType="fitCenter"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/fullscreen_button"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:src="@drawable/ic_fullscreen_black_48dp"
|
||||||
|
android:alpha="0.54"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_small"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:contentDescription="@string/show_qr_code_fullscreen"/>
|
||||||
|
</RelativeLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -92,5 +107,4 @@
|
|||||||
android:paddingTop="@dimen/margin_large"
|
android:paddingTop="@dimen/margin_large"
|
||||||
tools:text="@string/waiting_for_contact_to_scan"/>
|
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,23 +1,46 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<android.support.constraint.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="128dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/startup_lock"
|
||||||
|
android:tint="@color/briar_primary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/textView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.5"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
style="?android:attr/progressBarStyleLarge"
|
style="?android:attr/progressBarStyleLarge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_centerInParent="true"/>
|
app:layout_constraintBottom_toBottomOf="@+id/imageView"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/imageView"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/imageView"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/imageView"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title_progress_bar"
|
android:id="@+id/textView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/progressBar"
|
android:layout_margin="8dp"
|
||||||
android:layout_centerHorizontal="true"
|
android:text="@string/progress_title_logout"
|
||||||
android:paddingTop="@dimen/margin_large"
|
android:textSize="@dimen/text_size_large"
|
||||||
android:text="@string/progress_title_logout"/>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
@@ -94,13 +94,25 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
tools:visibility="gone"/>
|
tools:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/introductionNotPossibleView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_activity_horizontal"
|
||||||
|
android:text="@string/introduction_not_possible"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
<org.briarproject.briar.android.view.LargeTextInputView
|
<org.briarproject.briar.android.view.LargeTextInputView
|
||||||
android:id="@+id/introductionMessageView"
|
android:id="@+id/introductionMessageView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
app:buttonText="@string/introduction_button"
|
app:buttonText="@string/introduction_button"
|
||||||
app:hint="@string/introduction_message_hint"
|
app:hint="@string/introduction_message_hint"
|
||||||
app:maxLines="5"/>
|
app:maxLines="5"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -75,9 +75,7 @@
|
|||||||
<string name="show_onboarding">Показване на помощен диалог</string>
|
<string name="show_onboarding">Показване на помощен диалог</string>
|
||||||
<string name="help">Помощ</string>
|
<string name="help">Помощ</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Няма добавени контакти.\n\nНатиснете плюса и следвайте инструкциите, за да добавите приятели.\n\nВнимание: Трябва да се срещнете лично с човека, когото искате да добавите в Контакти. По този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
|
|
||||||
<string name="date_no_private_messages">Няма съобщения.</string>
|
<string name="date_no_private_messages">Няма съобщения.</string>
|
||||||
<string name="no_private_messages">Тук се показват съобщенията ви.\n\nНяма съобщения.</string>
|
|
||||||
<string name="message_hint">Напиши съобщение</string>
|
<string name="message_hint">Напиши съобщение</string>
|
||||||
<string name="delete_contact">Изтрий контакт</string>
|
<string name="delete_contact">Изтрий контакт</string>
|
||||||
<string name="dialog_title_delete_contact">Потвърди изтриването на контакт</string>
|
<string name="dialog_title_delete_contact">Потвърди изтриването на контакт</string>
|
||||||
@@ -122,7 +120,6 @@
|
|||||||
<item quantity="other">%d добавени нови контакти.</item>
|
<item quantity="other">%d добавени нови контакти.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Не сте включен в група.\n\nНатиснете плюса, за да създадете своя група, или помолете контактите си да ви добавят в техните групи.</string>
|
|
||||||
<string name="groups_created_by">Създаден от %s</string>
|
<string name="groups_created_by">Създаден от %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d съобщение</item>
|
<item quantity="one">%d съобщение</item>
|
||||||
@@ -175,12 +172,10 @@
|
|||||||
<string name="groups_reveal_visible_revealed_by_contact">Връзка с контакта се вижда от групата (разкрита от %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">Връзка с контакта се вижда от групата (разкрита от %s)</string>
|
||||||
<string name="groups_reveal_invisible">Връзка с контакта не се вижда от групата</string>
|
<string name="groups_reveal_invisible">Връзка с контакта не се вижда от групата</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">Няма добавени форуми.\n\nНатиснете плюса, за да създадете нов форум.\n\nМожете също да помолите контактите си да споделят форуми с вас.</string>
|
|
||||||
<string name="create_forum_title">Създаване на форум</string>
|
<string name="create_forum_title">Създаване на форум</string>
|
||||||
<string name="choose_forum_hint">Изберете име за форума</string>
|
<string name="choose_forum_hint">Изберете име за форума</string>
|
||||||
<string name="create_forum_button">Създай форум</string>
|
<string name="create_forum_button">Създай форум</string>
|
||||||
<string name="forum_created_toast">Форумът е създаден</string>
|
<string name="forum_created_toast">Форумът е създаден</string>
|
||||||
<string name="no_forum_posts">Форумът е празен.\n\nВъведете текст, за да съставите първата публикация.\n\nСподелете този форум с други ваши контакти!</string>
|
|
||||||
<string name="no_posts">Няма публикации</string>
|
<string name="no_posts">Няма публикации</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d публикация</item>
|
<item quantity="one">%d публикация</item>
|
||||||
@@ -192,23 +187,17 @@
|
|||||||
<string name="btn_reply">Отговори</string>
|
<string name="btn_reply">Отговори</string>
|
||||||
<string name="forum_leave">Напусни форума</string>
|
<string name="forum_leave">Напусни форума</string>
|
||||||
<string name="dialog_title_leave_forum">Потвърдете напускането на форума</string>
|
<string name="dialog_title_leave_forum">Потвърдете напускането на форума</string>
|
||||||
<string name="dialog_message_leave_forum">Сигурни ли сте, че искате да напуснете този форум? Контактите, с които сте споделили този форум, ще престанат да получават ъпдейти от него.</string>
|
|
||||||
<string name="dialog_button_leave">Напусни</string>
|
<string name="dialog_button_leave">Напусни</string>
|
||||||
<string name="forum_left_toast">Напуснахте форума</string>
|
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Сподели форум</string>
|
<string name="forum_share_button">Сподели форум</string>
|
||||||
<string name="contacts_selected">Избрани контакти</string>
|
<string name="contacts_selected">Избрани контакти</string>
|
||||||
<string name="activity_share_toolbar_header">Избиране на контакти</string>
|
<string name="activity_share_toolbar_header">Избиране на контакти</string>
|
||||||
<string name="no_contacts_selector">Няма добавени контакти.\n\nМоля, върнете се тук, когато добавите първия си контакт.</string>
|
|
||||||
<string name="forum_shared_snackbar">Форумът е споделен с избраните контакти</string>
|
<string name="forum_shared_snackbar">Форумът е споделен с избраните контакти</string>
|
||||||
<string name="forum_share_message">Добавете съобщение (незадължително)</string>
|
<string name="forum_share_message">Добавете съобщение (незадължително)</string>
|
||||||
<string name="forum_share_error">Възникна грешка при споделянето на този форум.</string>
|
<string name="forum_share_error">Възникна грешка при споделянето на този форум.</string>
|
||||||
<string name="forum_invitation_received">%1$s сподели форума \"%2$s\" с вас.</string>
|
<string name="forum_invitation_received">%1$s сподели форума \"%2$s\" с вас.</string>
|
||||||
<string name="forum_invitation_sent">Споделихте форума \"%1$s\" с %2$s.</string>
|
<string name="forum_invitation_sent">Споделихте форума \"%1$s\" с %2$s.</string>
|
||||||
<string name="forum_invitations_title">Покани във форум</string>
|
<string name="forum_invitations_title">Покани във форум</string>
|
||||||
<string name="forum_invitation_exists">Вече приехте поканата за този форум. Приемането на още покани ще подпомогне развитието на форума. </string>
|
|
||||||
<string name="forum_joined_toast">Включихте се във форума</string>
|
|
||||||
<string name="forum_declined_toast">Поканата във форум е отказана</string>
|
|
||||||
<string name="shared_by_format">Споделен от %s</string>
|
<string name="shared_by_format">Споделен от %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Вече е споделен</string>
|
<string name="forum_invitation_already_sharing">Вече е споделен</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Приехте поканата за форум на %s.</string>
|
<string name="forum_invitation_response_accepted_sent">Приехте поканата за форум на %s.</string>
|
||||||
@@ -224,19 +213,14 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Никого</string>
|
<string name="nobody">Никого</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="blogs_other_blog_empty_state">Блогът е празен.\n\nИли авторът още не е публикувал нищо, или е нужно да е онлайн човекът, който е споделил този блог с вас, за да се синхронизират публикациите.</string>
|
|
||||||
<string name="read_more">прочети още</string>
|
<string name="read_more">прочети още</string>
|
||||||
<string name="blogs_write_blog_post">Нова блог публикация</string>
|
<string name="blogs_write_blog_post">Нова блог публикация</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Въведете новата публикация тук</string>
|
|
||||||
<string name="blogs_publish_blog_post">Публикуване</string>
|
<string name="blogs_publish_blog_post">Публикуване</string>
|
||||||
<string name="blogs_blog_post_created">Блог публикацията е създадена</string>
|
<string name="blogs_blog_post_created">Блог публикацията е създадена</string>
|
||||||
<string name="blogs_blog_post_received">Нова блог публикация</string>
|
<string name="blogs_blog_post_received">Нова блог публикация</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Отвори</string>
|
<string name="blogs_blog_post_scroll_to">Отвори</string>
|
||||||
<string name="blogs_feed_empty_state">Това е глобалната блог емисия.\n\nНикой още не е публикувал нищо.\n\nБъдете първия и натиснете писалката, за да напишете първата блог публикация.</string>
|
|
||||||
<string name="blogs_remove_blog">Премахване на блог</string>
|
<string name="blogs_remove_blog">Премахване на блог</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че искате да премахнете този блог и всички публикации?\nБлогът няма да бъдат премахнат от устройствата на други хора.</string>
|
<string name="blogs_remove_blog_ok">Премахване</string>
|
||||||
<string name="blogs_remove_blog_ok">Премахни блог</string>
|
|
||||||
<string name="blogs_blog_removed">Блогът е премахнат</string>
|
|
||||||
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
|
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
|
||||||
<string name="blogs_reblog_button">Реблог</string>
|
<string name="blogs_reblog_button">Реблог</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
@@ -251,8 +235,6 @@
|
|||||||
<string name="blogs_sharing_invitation_received">%1$s сподели блога \"%2$s\" с вас.</string>
|
<string name="blogs_sharing_invitation_received">%1$s сподели блога \"%2$s\" с вас.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Споделихте блога \"%1$s\" с %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Споделихте блога \"%1$s\" с %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Блог покани</string>
|
<string name="blogs_sharing_invitations_title">Блог покани</string>
|
||||||
<string name="blogs_sharing_joined_toast">Абонирахте се за блога</string>
|
|
||||||
<string name="blogs_sharing_declined_toast">Поканата в блог е отказана</string>
|
|
||||||
<string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
<string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">Внасяне на RSS емисия</string>
|
<string name="blogs_rss_feeds_import">Внасяне на RSS емисия</string>
|
||||||
@@ -264,10 +246,8 @@
|
|||||||
<string name="blogs_rss_feeds_manage_author">Автор:</string>
|
<string name="blogs_rss_feeds_manage_author">Автор:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">Последно актуализиране:</string>
|
<string name="blogs_rss_feeds_manage_updated">Последно актуализиране:</string>
|
||||||
<string name="blogs_rss_remove_feed">Премахване на емисия</string>
|
<string name="blogs_rss_remove_feed">Премахване на емисия</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">Сигурни ли сте, че искате да премахнете тази емисия и всички нейни публикации?\nВсички споделени от вас публикации няма да бъдат премахнати от устройствата на други хора.</string>
|
<string name="blogs_rss_remove_feed_ok">Премахване</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Емисията е премахната</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">Емисията не можа да бъде изтрита!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">Емисията не можа да бъде изтрита!</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">Нямате добавени RSS емисии.\n\nНатиснете плюса в горния десен ъгъл на екрана, за да добавите нова емисия.</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string>
|
<string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<string name="network_settings_title">Мрежа</string>
|
<string name="network_settings_title">Мрежа</string>
|
||||||
|
|||||||
@@ -134,8 +134,10 @@
|
|||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string>
|
<string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
|
<string name="blogs_remove_blog_ok">Dilemel</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
|
<string name="blogs_rss_remove_feed_ok">Dilemel</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<!--Settings Security and Panic-->
|
<!--Settings Security and Panic-->
|
||||||
<string name="lock_setting_title">Digevreañ</string>
|
<string name="lock_setting_title">Digevreañ</string>
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
<string name="startup_failed_notification_title">Briar no s\'ha pogut iniciar</string>
|
<string name="startup_failed_notification_title">Briar no s\'ha pogut iniciar</string>
|
||||||
<string name="startup_failed_notification_text">Toqueu per obtenir més informació.</string>
|
<string name="startup_failed_notification_text">Toqueu per obtenir més informació.</string>
|
||||||
<string name="startup_failed_activity_title">Error iniciant Briar</string>
|
<string name="startup_failed_activity_title">Error iniciant Briar</string>
|
||||||
<string name="startup_failed_db_error">Per alguna raó, la vostra base de dades de Briar està malmesa més enllà de la reparació. El vostre compte, les vostres dades i tots els vostres contactes es perdran. Malauradament, necessiteu tornar a instal·lar Briar i configurar un nou compte escollint «He oblidat la meva contrasenya» quan se us demani la contrasenya.</string>
|
|
||||||
<string name="startup_failed_data_too_old_error">El vostre compte s\'ha creat amb una versió anterior d\'aquesta aplicació i no es pot obrir amb aquesta versió. Heu de tornar a instal·lar la versió antiga o eliminar el vostre antic compte escollint «He oblidat la meva contrasenya» quan se us demani la contrasenya.</string>
|
|
||||||
<string name="startup_failed_data_too_new_error">Aquesta versió de l\'aplicació és massa antiga. Actualitzeu a la versió més recent i torneu-ho a provar.</string>
|
<string name="startup_failed_data_too_new_error">Aquesta versió de l\'aplicació és massa antiga. Actualitzeu a la versió més recent i torneu-ho a provar.</string>
|
||||||
<string name="startup_failed_service_error">Briar no ha pogut engegar un connector necessari. La solució sol ser reinstal·lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
|
<string name="startup_failed_service_error">Briar no ha pogut engegar un connector necessari. La solució sol ser reinstal·lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
|
||||||
<plurals name="expiry_warning">
|
<plurals name="expiry_warning">
|
||||||
@@ -43,6 +41,8 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string>
|
<string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string>
|
||||||
<string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string>
|
<string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string>
|
||||||
|
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||||
|
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||||
<!--Navigation Drawer-->
|
<!--Navigation Drawer-->
|
||||||
<string name="nav_drawer_open_description">Obre el calaix de navegació</string>
|
<string name="nav_drawer_open_description">Obre el calaix de navegació</string>
|
||||||
<string name="nav_drawer_close_description">Tanca el calaix de navegació</string>
|
<string name="nav_drawer_close_description">Tanca el calaix de navegació</string>
|
||||||
@@ -99,9 +99,7 @@
|
|||||||
<string name="help">Ajuda</string>
|
<string name="help">Ajuda</string>
|
||||||
<string name="sorry">Ens sap greu</string>
|
<string name="sorry">Ens sap greu</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Sembla que sou nouvingut i no teniu contactes encara.\n\nToqueu la icona + en la part superior i seguiu les instruccions per a afegir amics a la llista.\n\nRecordeu: afegiu només contactes cara a cara per a evitar que algú es faci passar per vós o pugui llegir els vostres missatges en el futur. </string>
|
|
||||||
<string name="date_no_private_messages">Sense missatges.</string>
|
<string name="date_no_private_messages">Sense missatges.</string>
|
||||||
<string name="no_private_messages">Aquesta és la vista de conversa.\n\nSembla que hi manca una part de la conversa.\n\nNomés cal que toqueu el camp d\'entrada a la part inferior per iniciar una conversa.</string>
|
|
||||||
<string name="message_hint">Escriu el missatge.</string>
|
<string name="message_hint">Escriu el missatge.</string>
|
||||||
<string name="delete_contact">Suprimeix contacte</string>
|
<string name="delete_contact">Suprimeix contacte</string>
|
||||||
<string name="dialog_title_delete_contact">Confirma la supressió del contacte</string>
|
<string name="dialog_title_delete_contact">Confirma la supressió del contacte</string>
|
||||||
@@ -149,7 +147,6 @@
|
|||||||
<item quantity="other">S\'ha afegit %d nous contactes.</item>
|
<item quantity="other">S\'ha afegit %d nous contactes.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">No pertanyeu a cap grup.\n\nToqueu la icona + en la part superior per a crear un grup o demaneu als vostres contactes que us conviden a un grup.</string>
|
|
||||||
<string name="groups_created_by">Creat per 1%s</string>
|
<string name="groups_created_by">Creat per 1%s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">1%d missatge</item>
|
<item quantity="one">1%d missatge</item>
|
||||||
@@ -202,12 +199,11 @@
|
|||||||
<string name="groups_reveal_visible_revealed_by_contact">La relació del contacte és visible per al grup (revelat per %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">La relació del contacte és visible per al grup (revelat per %s)</string>
|
||||||
<string name="groups_reveal_invisible">La relació del contacte no és visible per al grup</string>
|
<string name="groups_reveal_invisible">La relació del contacte no és visible per al grup</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">No teniu cap fòrum encara.\n\nPer què no en creeu un de nou tocant el + que hi ha a la part superior dreta?\n\nTambé podeu demanar els vostres contactes que us conviden a fòrums.</string>
|
|
||||||
<string name="create_forum_title">Crea un fòrum</string>
|
<string name="create_forum_title">Crea un fòrum</string>
|
||||||
<string name="choose_forum_hint">Trieu un nom per al fòrum</string>
|
<string name="choose_forum_hint">Trieu un nom per al fòrum</string>
|
||||||
<string name="create_forum_button">Crea el fòrum</string>
|
<string name="create_forum_button">Crea el fòrum</string>
|
||||||
<string name="forum_created_toast">S\'ha creat el fòrum</string>
|
<string name="forum_created_toast">S\'ha creat el fòrum</string>
|
||||||
<string name="no_forum_posts">Aquest fòrum està buit.\n\nUtilitzeu la icona de la ploma a la part superior per redactar la primera publicació.\n\nEsteu aquí sol? Compartiu aquest fòrum amb els vostres contactes!</string>
|
<string name="no_forum_posts">No hi ha publicacions per mostrar</string>
|
||||||
<string name="no_posts">No hi ha publicacions</string>
|
<string name="no_posts">No hi ha publicacions</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d publicacio</item>
|
<item quantity="one">%d publicacio</item>
|
||||||
@@ -219,23 +215,17 @@
|
|||||||
<string name="btn_reply">Respon</string>
|
<string name="btn_reply">Respon</string>
|
||||||
<string name="forum_leave">Abandona el fòrum</string>
|
<string name="forum_leave">Abandona el fòrum</string>
|
||||||
<string name="dialog_title_leave_forum">Confirmeu la sortida del Forum</string>
|
<string name="dialog_title_leave_forum">Confirmeu la sortida del Forum</string>
|
||||||
<string name="dialog_message_leave_forum">Esteu segur que voleu sortir del fòrum? Els contactes amb qui heu compartit aquest fòrum podrien deixar de rebre\'n actualitzacions.</string>
|
|
||||||
<string name="dialog_button_leave">Abandona</string>
|
<string name="dialog_button_leave">Abandona</string>
|
||||||
<string name="forum_left_toast">Abandona el fòrum</string>
|
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Comparteix el fòrum</string>
|
<string name="forum_share_button">Comparteix el fòrum</string>
|
||||||
<string name="contacts_selected">Contactes seleccionats</string>
|
<string name="contacts_selected">Contactes seleccionats</string>
|
||||||
<string name="activity_share_toolbar_header">Tria contactes</string>
|
<string name="activity_share_toolbar_header">Tria contactes</string>
|
||||||
<string name="no_contacts_selector">Sembla que sou nou aquí i no teniu cap contacte.\n\nTorneu aquí després d\'afegir el primer contacte.</string>
|
|
||||||
<string name="forum_shared_snackbar">Fòrum compartit amb els contactes seleccionats</string>
|
<string name="forum_shared_snackbar">Fòrum compartit amb els contactes seleccionats</string>
|
||||||
<string name="forum_share_message">Afegiu un missatge (opcional)</string>
|
<string name="forum_share_message">Afegiu un missatge (opcional)</string>
|
||||||
<string name="forum_share_error">S\'ha produït un error en compartir aquest fòrum.</string>
|
<string name="forum_share_error">S\'ha produït un error en compartir aquest fòrum.</string>
|
||||||
<string name="forum_invitation_received">%1$s ha compartit el fòrum «%2$s» amb vós.</string>
|
<string name="forum_invitation_received">%1$s ha compartit el fòrum «%2$s» amb vós.</string>
|
||||||
<string name="forum_invitation_sent">Heu compartit el fòrum «%1$s» amb %2$s.</string>
|
<string name="forum_invitation_sent">Heu compartit el fòrum «%1$s» amb %2$s.</string>
|
||||||
<string name="forum_invitations_title">Invitacions al fòrum</string>
|
<string name="forum_invitations_title">Invitacions al fòrum</string>
|
||||||
<string name="forum_invitation_exists">Ja heu acceptat una invitació a aquest fòrum. L\'acceptació de més invitacions augmentarà i enfortirà la comunicació al fòrum.</string>
|
|
||||||
<string name="forum_joined_toast">Us hi heu unit al fòrum</string>
|
|
||||||
<string name="forum_declined_toast">S\'ha rebutjat la invitació al fòrum</string>
|
|
||||||
<string name="shared_by_format">Compartit per 1%s</string>
|
<string name="shared_by_format">Compartit per 1%s</string>
|
||||||
<string name="forum_invitation_already_sharing">Ja esteu compartint</string>
|
<string name="forum_invitation_already_sharing">Ja esteu compartint</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació del fòrum de %s.</string>
|
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació del fòrum de %s.</string>
|
||||||
@@ -251,19 +241,15 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Ningú</string>
|
<string name="nobody">Ningú</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="blogs_other_blog_empty_state">Aquest blog està buit.\n\nPotser l\'autor encara no hi ha escrit res o que la persona que us ha compartit aquest blog hagi de connectar-se, llavors es podran sincronitzar les publicacions.</string>
|
<string name="blogs_other_blog_empty_state">No hi ha publicacions per mostrar</string>
|
||||||
<string name="read_more">llegir més</string>
|
<string name="read_more">llegir més</string>
|
||||||
<string name="blogs_write_blog_post">Escriviu una publicació de blog</string>
|
<string name="blogs_write_blog_post">Escriviu una publicació de blog</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Escriviu la vostra publicació al blog aquí</string>
|
|
||||||
<string name="blogs_publish_blog_post">Publica</string>
|
<string name="blogs_publish_blog_post">Publica</string>
|
||||||
<string name="blogs_blog_post_created">S\'ha creat la publicació al blog</string>
|
<string name="blogs_blog_post_created">S\'ha creat la publicació al blog</string>
|
||||||
<string name="blogs_blog_post_received">S\'ha rebut una nova publicació al blog</string>
|
<string name="blogs_blog_post_received">S\'ha rebut una nova publicació al blog</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Desplaça</string>
|
<string name="blogs_blog_post_scroll_to">Desplaça</string>
|
||||||
<string name="blogs_feed_empty_state">Aquestes són totes les entrades del blog.\n\nSembla que ningú ha publicat res encara.\n\nSigueu el primer i premeu la icona del llapis per escriure una publicació nova al blog.</string>
|
|
||||||
<string name="blogs_remove_blog">Elimina el blog</string>
|
<string name="blogs_remove_blog">Elimina el blog</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">Esteu segur que voleu esborrar aquest blog i totes les seves publicacions?\nTingueu present que això no esborrarà el blog dels dispositius d\'altres persones.</string>
|
<string name="blogs_remove_blog_ok">Eliminar</string>
|
||||||
<string name="blogs_remove_blog_ok">Elimina el blog</string>
|
|
||||||
<string name="blogs_blog_removed">S\'ha eliminat el blog</string>
|
|
||||||
<string name="blogs_reblog_comment_hint">Afegiu un comentari (opcional)</string>
|
<string name="blogs_reblog_comment_hint">Afegiu un comentari (opcional)</string>
|
||||||
<string name="blogs_reblog_button">Rebloga</string>
|
<string name="blogs_reblog_button">Rebloga</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
@@ -278,8 +264,6 @@
|
|||||||
<string name="blogs_sharing_invitation_received">%1$s us ha compartit el blog \"%2$s\".</string>
|
<string name="blogs_sharing_invitation_received">%1$s us ha compartit el blog \"%2$s\".</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Heu compartit el blog \"%1$s\" amb %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Heu compartit el blog \"%1$s\" amb %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Invitacions al blog</string>
|
<string name="blogs_sharing_invitations_title">Invitacions al blog</string>
|
||||||
<string name="blogs_sharing_joined_toast">Subscrit al blog</string>
|
|
||||||
<string name="blogs_sharing_declined_toast">Invitació al blog refusada</string>
|
|
||||||
<string name="sharing_status_blog">Qualsevol subscrit a un blog el pot compartir amb els seus contactes. Esteu compartint aquest blog amb els següents contactes. També hi pot haver altres subscrits que no veieu.</string>
|
<string name="sharing_status_blog">Qualsevol subscrit a un blog el pot compartir amb els seus contactes. Esteu compartint aquest blog amb els següents contactes. També hi pot haver altres subscrits que no veieu.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">Importa canal RSS</string>
|
<string name="blogs_rss_feeds_import">Importa canal RSS</string>
|
||||||
@@ -291,10 +275,8 @@
|
|||||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">Darrera actualització:</string>
|
<string name="blogs_rss_feeds_manage_updated">Darrera actualització:</string>
|
||||||
<string name="blogs_rss_remove_feed">Elimina el canal</string>
|
<string name="blogs_rss_remove_feed">Elimina el canal</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">Esteu segurs que voleu eliminar aquest canal i totes les seves publicacions?\nLes publicacions que hàgiu compartit no se suprimiran dels dispositius d\'altres persones.</string>
|
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Elimina el canal</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string>
|
<string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">No heu importat cap canal RSS.\n\nPer què no feu clic al botó més de la part superior dreta per afegir el primer?</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string>
|
<string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<string name="network_settings_title">Xarxes</string>
|
<string name="network_settings_title">Xarxes</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user