mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +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,37 +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.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
|
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||||
|
|
||||||
|
// See android.net.wifi.WifiManager
|
||||||
|
private static final String WIFI_AP_STATE_CHANGED_ACTION =
|
||||||
|
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
||||||
|
private static final int WIFI_AP_STATE_ENABLED = 13;
|
||||||
|
|
||||||
|
private static final byte[] WIFI_AP_ADDRESS_BYTES =
|
||||||
|
{(byte) 192, (byte) 168, 43, 1};
|
||||||
|
private static final InetAddress WIFI_AP_ADDRESS;
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should only be thrown if the address has an illegal length
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
private final ConnectivityManager connectivityManager;
|
||||||
|
@Nullable
|
||||||
|
private final WifiManager wifiManager;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
private volatile SocketFactory socketFactory;
|
||||||
|
|
||||||
AndroidLanTcpPlugin(Executor ioExecutor, 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
|
||||||
@@ -44,7 +91,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
running = true;
|
running = true;
|
||||||
// Register to receive network status events
|
// Register to receive network status events
|
||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
|
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +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;
|
||||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
if (isApEnabledEvent(i)) {
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
// The state change may be broadcast before the AP address is
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
// visible, so delay handling the event
|
||||||
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
|
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
|
||||||
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,56 +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);
|
||||||
@@ -788,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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -99,9 +99,7 @@
|
|||||||
<string name="fix">Opravit</string>
|
<string name="fix">Opravit</string>
|
||||||
<string name="help">Pomoc</string>
|
<string name="help">Pomoc</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Zdá se, že tu jste noví a dosud nemáte žádné kontakty.\n\nKliknětě na + v horní části a sledujte instrukce jak přidat kontakty.\n\n Prosím pamatuj: Můžeš přidat pouze nové kontakty, se kterými jsi tváří-tvář. Je to prevence situacím, kdy se za Tebe bude chtít někdo vydávat, či bude chtít číst Tvé zprávy.</string>
|
|
||||||
<string name="date_no_private_messages">Žádné zprávy</string>
|
<string name="date_no_private_messages">Žádné zprávy</string>
|
||||||
<string name="no_private_messages">Toto je konverzace a její náhled.\n\nZdá se, že postrádáte konverzace.\n\nKliknutím na vstupní pole v dolní části zahajte konverzaci.</string>
|
|
||||||
<string name="message_hint">Psát zprávu</string>
|
<string name="message_hint">Psát zprávu</string>
|
||||||
<string name="delete_contact">Odstranit kontakt</string>
|
<string name="delete_contact">Odstranit kontakt</string>
|
||||||
<string name="dialog_title_delete_contact">Potvrdit odstranění kontaktu</string>
|
<string name="dialog_title_delete_contact">Potvrdit odstranění kontaktu</string>
|
||||||
@@ -149,7 +147,6 @@
|
|||||||
<item quantity="other">%d nových kontaktů bylo přidáno.</item>
|
<item quantity="other">%d nových kontaktů bylo přidáno.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Nepodílíte se na žádné skupině.\n\nKlikněte na ikonu + výše pro vytvoření vaší skupiny nebo požádejte některého z vašich kontaktů o pozvání do některých skupin.</string>
|
|
||||||
<string name="groups_created_by">Vytvořeno %s</string>
|
<string name="groups_created_by">Vytvořeno %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d zpráva</item>
|
<item quantity="one">%d zpráva</item>
|
||||||
@@ -204,12 +201,10 @@
|
|||||||
<string name="groups_reveal_visible_revealed_by_contact">Vztah s kontaktem je viditelný ve skupině (byl odkrytý %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">Vztah s kontaktem je viditelný ve skupině (byl odkrytý %s)</string>
|
||||||
<string name="groups_reveal_invisible">Vztah s kontaktem není viditelný ve skupině</string>
|
<string name="groups_reveal_invisible">Vztah s kontaktem není viditelný ve skupině</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">Dosud nemáte žádné fórum.\n\nProč si nějaké své nevytvoříte kliknutím v horní části na symbol +?\n\nMůžete taktéž požádat své kontakty o sdílení některých fór s vámi.</string>
|
|
||||||
<string name="create_forum_title">Vytvořit fórum</string>
|
<string name="create_forum_title">Vytvořit fórum</string>
|
||||||
<string name="choose_forum_hint">Vybrat jméno pro vaše fórum</string>
|
<string name="choose_forum_hint">Vybrat jméno pro vaše fórum</string>
|
||||||
<string name="create_forum_button">Vytvořit fórum</string>
|
<string name="create_forum_button">Vytvořit fórum</string>
|
||||||
<string name="forum_created_toast">Fórum vytvořeno</string>
|
<string name="forum_created_toast">Fórum vytvořeno</string>
|
||||||
<string name="no_forum_posts">Toto fórum je prázdné.\n\nPoužij ikonu tužky nahoře k vytvoření prvního příspěvku.\n\nCítíš se tu dobře? Sdílej toto fórum s vícero svými kontakty.</string>
|
|
||||||
<string name="no_posts">Žádné příspěvky</string>
|
<string name="no_posts">Žádné příspěvky</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d příspěvek</item>
|
<item quantity="one">%d příspěvek</item>
|
||||||
@@ -222,23 +217,17 @@
|
|||||||
<string name="btn_reply">Odpověď</string>
|
<string name="btn_reply">Odpověď</string>
|
||||||
<string name="forum_leave">Opustit fórum</string>
|
<string name="forum_leave">Opustit fórum</string>
|
||||||
<string name="dialog_title_leave_forum">Potvrdit opuštění fóra</string>
|
<string name="dialog_title_leave_forum">Potvrdit opuštění fóra</string>
|
||||||
<string name="dialog_message_leave_forum">Opravdu chcete opustit toto fórum? Kontakty, které jste sdíleli s tímto fórem, mohou být odebrány z příjmu aktualizací tohoto fóra.</string>
|
|
||||||
<string name="dialog_button_leave">Opustit</string>
|
<string name="dialog_button_leave">Opustit</string>
|
||||||
<string name="forum_left_toast">Opustit fórum</string>
|
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Sdílet fórum</string>
|
<string name="forum_share_button">Sdílet fórum</string>
|
||||||
<string name="contacts_selected">Zvolené kontakty</string>
|
<string name="contacts_selected">Zvolené kontakty</string>
|
||||||
<string name="activity_share_toolbar_header">Zvolit kontakty</string>
|
<string name="activity_share_toolbar_header">Zvolit kontakty</string>
|
||||||
<string name="no_contacts_selector">Zdá se, že jste tu noví a doposud nemáte žádné kontakty.\n\nProsím, vraťte se zpět po přidání svého prvního kontaktu.</string>
|
|
||||||
<string name="forum_shared_snackbar">Fórum sdíleno s vybranými kontakty</string>
|
<string name="forum_shared_snackbar">Fórum sdíleno s vybranými kontakty</string>
|
||||||
<string name="forum_share_message">Přidat zprávu (volitelné)</string>
|
<string name="forum_share_message">Přidat zprávu (volitelné)</string>
|
||||||
<string name="forum_share_error">Při sdílení tohoto fóra nastala chyba.</string>
|
<string name="forum_share_error">Při sdílení tohoto fóra nastala chyba.</string>
|
||||||
<string name="forum_invitation_received">%1$s sdílel fórum \"%2$s\" s vámi.</string>
|
<string name="forum_invitation_received">%1$s sdílel fórum \"%2$s\" s vámi.</string>
|
||||||
<string name="forum_invitation_sent">Sdíleli jste fórum \"%1$s\" s %2$s.</string>
|
<string name="forum_invitation_sent">Sdíleli jste fórum \"%1$s\" s %2$s.</string>
|
||||||
<string name="forum_invitations_title">Pozvání do fóra</string>
|
<string name="forum_invitations_title">Pozvání do fóra</string>
|
||||||
<string name="forum_invitation_exists">Již jste přijali pozvání do tohoto fóra. Přijímání dalších pozvánek povede a posílí komunikaci ve fóru.</string>
|
|
||||||
<string name="forum_joined_toast">Vstoupit do fóra</string>
|
|
||||||
<string name="forum_declined_toast">Pozvánka do fóra byla zamítnuta</string>
|
|
||||||
<string name="shared_by_format">Sdíleno %s</string>
|
<string name="shared_by_format">Sdíleno %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Již sdílené</string>
|
<string name="forum_invitation_already_sharing">Již sdílené</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Přijali jste pozvání do fóra od %s.</string>
|
<string name="forum_invitation_response_accepted_sent">Přijali jste pozvání do fóra od %s.</string>
|
||||||
@@ -255,19 +244,14 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Nikdo</string>
|
<string name="nobody">Nikdo</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="blogs_other_blog_empty_state">Tento blog je momentálně prázdný.\n\nPokud autor nebo osoba, sdílící s vámi tento blog, ještě nic nenapsali, pro synchronizaci příspěvků je tedy nutné, aby byli online.</string>
|
|
||||||
<string name="read_more">Číst dál</string>
|
<string name="read_more">Číst dál</string>
|
||||||
<string name="blogs_write_blog_post">Napsat příspěvek do Blogu</string>
|
<string name="blogs_write_blog_post">Napsat příspěvek do Blogu</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Svůj příspěvek do blogu zadejte zde</string>
|
|
||||||
<string name="blogs_publish_blog_post">Publikovat</string>
|
<string name="blogs_publish_blog_post">Publikovat</string>
|
||||||
<string name="blogs_blog_post_created">Příspěvek do blogu byl vytvořen</string>
|
<string name="blogs_blog_post_created">Příspěvek do blogu byl vytvořen</string>
|
||||||
<string name="blogs_blog_post_received">Přijatý nový příspěvek v blogu.</string>
|
<string name="blogs_blog_post_received">Přijatý nový příspěvek v blogu.</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Přejít na</string>
|
<string name="blogs_blog_post_scroll_to">Přejít na</string>
|
||||||
<string name="blogs_feed_empty_state">Toto je globální kanál blogu.\n\nZdá se, že ještě nikdo neblogoval..\n\nBuďte první, klikněte na ikonu tužky a napište do blogu nový příspěvek.</string>
|
|
||||||
<string name="blogs_remove_blog">Odstranit blog</string>
|
<string name="blogs_remove_blog">Odstranit blog</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">Jste si jisti, že chcete odstranit tento blog a všechny příspěvky v něm?\nPamatujte, že blog nebude odstraněn ze zařízení ostatních lidí.</string>
|
<string name="blogs_remove_blog_ok">Odstranit</string>
|
||||||
<string name="blogs_remove_blog_ok">Odstranit blog</string>
|
|
||||||
<string name="blogs_blog_removed">Blog byl odstraněn</string>
|
|
||||||
<string name="blogs_reblog_comment_hint">Přidat komentář (volitelné)</string>
|
<string name="blogs_reblog_comment_hint">Přidat komentář (volitelné)</string>
|
||||||
<string name="blogs_reblog_button">Reblog</string>
|
<string name="blogs_reblog_button">Reblog</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
@@ -282,8 +266,6 @@
|
|||||||
<string name="blogs_sharing_invitation_received">%1$s sdílel blog \"%2$s\" s vámi.</string>
|
<string name="blogs_sharing_invitation_received">%1$s sdílel blog \"%2$s\" s vámi.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Sdíleli jste blog \"%1$s\" s %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Sdíleli jste blog \"%1$s\" s %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Pozvánky do blogu</string>
|
<string name="blogs_sharing_invitations_title">Pozvánky do blogu</string>
|
||||||
<string name="blogs_sharing_joined_toast">Přihlásit se k blogu</string>
|
|
||||||
<string name="blogs_sharing_declined_toast">Pozvání do blogu bylo zamítnuto</string>
|
|
||||||
<string name="sharing_status_blog">Kdokoli, kdo odebírá blog ho může sdílet se svými kontakty. Sdílíte tento blog s následujícími kontakty. Mohou existovat i jiní účastníci, které nevidíte.</string>
|
<string name="sharing_status_blog">Kdokoli, kdo odebírá blog ho může sdílet se svými kontakty. Sdílíte tento blog s následujícími kontakty. Mohou existovat i jiní účastníci, které nevidíte.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">Import RSS kanálu</string>
|
<string name="blogs_rss_feeds_import">Import RSS kanálu</string>
|
||||||
@@ -295,10 +277,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">Naposledy aktualizováno:</string>
|
<string name="blogs_rss_feeds_manage_updated">Naposledy aktualizováno:</string>
|
||||||
<string name="blogs_rss_remove_feed">Odstranit kanál</string>
|
<string name="blogs_rss_remove_feed">Odstranit kanál</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">Skutečně chceš smazat tento kanál a s ním spojené příspěvky?\nVeškeré příspěvky, které jste sdíleli, nebudou odstraněny ze zařízení jiných lidí.</string>
|
<string name="blogs_rss_remove_feed_ok">Odstranit</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Odstranit kanál</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">Kanál nemohl být odstraněn !</string>
|
<string name="blogs_rss_feeds_manage_delete_error">Kanál nemohl být odstraněn !</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">Nemáte naimportovaný žádný RSS kanál.\n\nProč nekliknete na znak plus v horní části obrazovky, kde můžete přidat svůj první zdroj?</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_error">Vyskytl se problém s načtením vašeho kanálu příspěvků. Zkuste to prosím později.</string>
|
<string name="blogs_rss_feeds_manage_error">Vyskytl se problém s načtením vašeho kanálu příspěvků. Zkuste to prosím později.</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<string name="network_settings_title">Sítě</string>
|
<string name="network_settings_title">Sítě</string>
|
||||||
|
|||||||
@@ -94,9 +94,7 @@
|
|||||||
<string name="fix">Behoben</string>
|
<string name="fix">Behoben</string>
|
||||||
<string name="help">Hilfe</string>
|
<string name="help">Hilfe</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nTippe auf das \"+\"-Symbol am oberen Bildschirmrand und folge den Anweisungen, um Freunde zu deiner Kontaktliste hinzuzufügen.\n\nDenke daran, dass du neue Kontakte nur dann hinzufügen kannst, wenn du ihnen persönlich begegnest. Das hindert andere daran, sich als deine Person auszugeben oder deine Nachrichten zu lesen.</string>
|
|
||||||
<string name="date_no_private_messages">Keine Nachrichten.</string>
|
<string name="date_no_private_messages">Keine Nachrichten.</string>
|
||||||
<string name="no_private_messages">Das ist die Gesprächsansicht.\n\nEs scheint hier einen Mangel an Gesprächsthemen zu geben.\n\nUm ein neues Gespräch zu beginnen, verwende einfach das Eingabefeld am unteren Bildschirmrand.</string>
|
|
||||||
<string name="message_hint">Nachricht eingeben</string>
|
<string name="message_hint">Nachricht eingeben</string>
|
||||||
<string name="delete_contact">Kontakt löschen</string>
|
<string name="delete_contact">Kontakt löschen</string>
|
||||||
<string name="dialog_title_delete_contact">Löschen des Kontakts bestätigen</string>
|
<string name="dialog_title_delete_contact">Löschen des Kontakts bestätigen</string>
|
||||||
@@ -143,7 +141,6 @@
|
|||||||
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
|
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Du bist in keiner Gruppe Mitglied.\n\nTippe auf das \"+\"-Symbol am oberen Bildschirmrand, um eine Gruppe zu erstellen, oder frage deine Kontakte um Aufnahme in einer ihrer Gruppen.</string>
|
|
||||||
<string name="groups_created_by">Erstellt durch %s</string>
|
<string name="groups_created_by">Erstellt durch %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d Nachrichten</item>
|
<item quantity="one">%d Nachrichten</item>
|
||||||
@@ -196,12 +193,10 @@
|
|||||||
<string name="groups_reveal_visible_revealed_by_contact">Verbindung zum Kontakt ist für diese Gruppe sichtbar (offengelegt durch %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">Verbindung zum Kontakt ist für diese Gruppe sichtbar (offengelegt durch %s)</string>
|
||||||
<string name="groups_reveal_invisible">Verbindung zum Kontakt ist für diese Gruppe nicht sichtbar</string>
|
<string name="groups_reveal_invisible">Verbindung zum Kontakt ist für diese Gruppe nicht sichtbar</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">Du hast noch keine Foren.\n\nWarum erstellst du nicht einfach selbst ein neues Forum, indem du auf das \"+\"-Symbol am oberen Bildschirmrand tippst?\n\nDu kannst auch deine Kontakte auffordern, Foren mit dir zu teilen.</string>
|
|
||||||
<string name="create_forum_title">Forum erstellen</string>
|
<string name="create_forum_title">Forum erstellen</string>
|
||||||
<string name="choose_forum_hint">Wähle einen Namen für dein Forum</string>
|
<string name="choose_forum_hint">Wähle einen Namen für dein Forum</string>
|
||||||
<string name="create_forum_button">Forum erstellen</string>
|
<string name="create_forum_button">Forum erstellen</string>
|
||||||
<string name="forum_created_toast">Forum wurde erstellt</string>
|
<string name="forum_created_toast">Forum wurde erstellt</string>
|
||||||
<string name="no_forum_posts">Dieses Forum ist leer.\n\nBenutze das Stift-Symbol am oberen Bildschirmrand, um den ersten Beitrag zu verfassen.\n\nFühlst du dich einsam hier? Dann teile das Forum mit weiteren Kontakten!</string>
|
|
||||||
<string name="no_posts">Keine Beiträge</string>
|
<string name="no_posts">Keine Beiträge</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d Beitrag</item>
|
<item quantity="one">%d Beitrag</item>
|
||||||
@@ -213,23 +208,17 @@
|
|||||||
<string name="btn_reply">Antworten</string>
|
<string name="btn_reply">Antworten</string>
|
||||||
<string name="forum_leave">Forum verlassen</string>
|
<string name="forum_leave">Forum verlassen</string>
|
||||||
<string name="dialog_title_leave_forum">Verlassen des Forums bestätigen</string>
|
<string name="dialog_title_leave_forum">Verlassen des Forums bestätigen</string>
|
||||||
<string name="dialog_message_leave_forum">Bist du sicher, dass du dieses Forum verlassen willst? Kontakte, die du mit diesem Forum geteilt hast, werden keine Updates von diesem Forum mehr bekommen.</string>
|
|
||||||
<string name="dialog_button_leave">Verlassen</string>
|
<string name="dialog_button_leave">Verlassen</string>
|
||||||
<string name="forum_left_toast">Forum wurde verlassen</string>
|
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Forum teilen</string>
|
<string name="forum_share_button">Forum teilen</string>
|
||||||
<string name="contacts_selected">Ausgewählte Kontakte</string>
|
<string name="contacts_selected">Ausgewählte Kontakte</string>
|
||||||
<string name="activity_share_toolbar_header">Kontakte auswählen</string>
|
<string name="activity_share_toolbar_header">Kontakte auswählen</string>
|
||||||
<string name="no_contacts_selector">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nBitte komm zurück, wenn du deinen ersten Kontakt hinzugefügt hast.</string>
|
|
||||||
<string name="forum_shared_snackbar">Forum mit gewählten Kontakten geteilt</string>
|
<string name="forum_shared_snackbar">Forum mit gewählten Kontakten geteilt</string>
|
||||||
<string name="forum_share_message">Nachricht hinzufügen (optional)</string>
|
<string name="forum_share_message">Nachricht hinzufügen (optional)</string>
|
||||||
<string name="forum_share_error">Es gab einen Fehler beim Versuch, dieses Forum zu teilen.</string>
|
<string name="forum_share_error">Es gab einen Fehler beim Versuch, dieses Forum zu teilen.</string>
|
||||||
<string name="forum_invitation_received">%1$s hat das Forum \"%2$s\" mit dir geteilt.</string>
|
<string name="forum_invitation_received">%1$s hat das Forum \"%2$s\" mit dir geteilt.</string>
|
||||||
<string name="forum_invitation_sent">Du hast das Forum \"%1$s\" mit %2$s geteilt.</string>
|
<string name="forum_invitation_sent">Du hast das Forum \"%1$s\" mit %2$s geteilt.</string>
|
||||||
<string name="forum_invitations_title">Foreneinladungen</string>
|
<string name="forum_invitations_title">Foreneinladungen</string>
|
||||||
<string name="forum_invitation_exists">Du hast bereits eine Einladung zu diesem Forum angenommen. Wenn du weitere Einladungen annimmst, wird die Kommunikation in dem Forum einfacher und das Forum wird wachsen.</string>
|
|
||||||
<string name="forum_joined_toast">Dem Forum beigetreten</string>
|
|
||||||
<string name="forum_declined_toast">Foreneinladung abgelehnt</string>
|
|
||||||
<string name="shared_by_format">Geteilt durch %s</string>
|
<string name="shared_by_format">Geteilt durch %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
|
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Du hast die Forumseinladung von %s akzeptiert.</string>
|
<string name="forum_invitation_response_accepted_sent">Du hast die Forumseinladung von %s akzeptiert.</string>
|
||||||
@@ -245,19 +234,14 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Niemand</string>
|
<string name="nobody">Niemand</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="blogs_other_blog_empty_state">Dieser Blog ist momentan leer.\n\nEntweder hat der Autor noch nichts geschrieben, oder die Person, die diesen Blog mit dir geteilt hat, muss online gehen, sodass Beiträge synchronisiert werden können.</string>
|
|
||||||
<string name="read_more">weiterlesen</string>
|
<string name="read_more">weiterlesen</string>
|
||||||
<string name="blogs_write_blog_post">Blogbeitrag erstellen</string>
|
<string name="blogs_write_blog_post">Blogbeitrag erstellen</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Gib hier deinen Blogbeitrag ein</string>
|
|
||||||
<string name="blogs_publish_blog_post">Veröffentlichen</string>
|
<string name="blogs_publish_blog_post">Veröffentlichen</string>
|
||||||
<string name="blogs_blog_post_created">Blogbeitrag erstellt</string>
|
<string name="blogs_blog_post_created">Blogbeitrag erstellt</string>
|
||||||
<string name="blogs_blog_post_received">Neuen Blogbeitrag empfangen</string>
|
<string name="blogs_blog_post_received">Neuen Blogbeitrag empfangen</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Scrolle zu</string>
|
<string name="blogs_blog_post_scroll_to">Scrolle zu</string>
|
||||||
<string name="blogs_feed_empty_state">Dies ist die globale Blog-Zeitleiste.\n\nOffensichtlich hat noch niemand etwas veröffentlicht.\n\nSei die oder der Erste und tippe auf das Stift-Symbol, um einen neuen Blogbeitrag zu verfassen.</string>
|
|
||||||
<string name="blogs_remove_blog">Blog entfernen</string>
|
<string name="blogs_remove_blog">Blog entfernen</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">Bist du sicher, dass du diesen Blog und alle dazugehörigen Beiträge löschen möchtest?\nBeachte, dass dies nicht den Blog auf Geräten anderer Leute löscht.</string>
|
<string name="blogs_remove_blog_ok">Aufheben</string>
|
||||||
<string name="blogs_remove_blog_ok">Blog entfernen</string>
|
|
||||||
<string name="blogs_blog_removed">Blog wurde entfernt</string>
|
|
||||||
<string name="blogs_reblog_comment_hint">Kommentar hinzufügen (optional)</string>
|
<string name="blogs_reblog_comment_hint">Kommentar hinzufügen (optional)</string>
|
||||||
<string name="blogs_reblog_button">Rebloggen</string>
|
<string name="blogs_reblog_button">Rebloggen</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
@@ -272,8 +256,6 @@
|
|||||||
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
|
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Du teilst den Blog \"%1$s\" mit %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Du teilst den Blog \"%1$s\" mit %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
|
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
|
||||||
<string name="blogs_sharing_joined_toast">Blog abonniert</string>
|
|
||||||
<string name="blogs_sharing_declined_toast">Blogeinladung abgelehnt</string>
|
|
||||||
<string name="sharing_status_blog">Jeder Abonnent eines Blogs kann diesen mit seinen Kontakten teilen. Du teilst diesen Blog mit den folgenden Kontakten. Möglicherweise gibt es Abonnenten die nicht sichtbar sind.</string>
|
<string name="sharing_status_blog">Jeder Abonnent eines Blogs kann diesen mit seinen Kontakten teilen. Du teilst diesen Blog mit den folgenden Kontakten. Möglicherweise gibt es Abonnenten die nicht sichtbar sind.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">RSS-Feed importieren</string>
|
<string name="blogs_rss_feeds_import">RSS-Feed importieren</string>
|
||||||
@@ -285,10 +267,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">Letzte Aktualisierung:</string>
|
<string name="blogs_rss_feeds_manage_updated">Letzte Aktualisierung:</string>
|
||||||
<string name="blogs_rss_remove_feed">Feed entfernen</string>
|
<string name="blogs_rss_remove_feed">Feed entfernen</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">Soll der Feed mit allen Posts wirklich gelöscht werden?\nGeteilte Posts werden dabei nicht von anderen Geräten gelöscht.</string>
|
<string name="blogs_rss_remove_feed_ok">Aufheben</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Feed entfernen</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">Der Feed konnte nicht gelöscht werden!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">Der Feed konnte nicht gelöscht werden!</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">Du hast bisher noch keine RSS-Feeds importiert. Tippe auf das \"+\"-Symbol am oberen Bildschirmrand, um einen neuen Feed hinzuzufügen.</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string>
|
<string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<string name="network_settings_title">Netzwerke</string>
|
<string name="network_settings_title">Netzwerke</string>
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
<string name="startup_failed_notification_title">Briar no pudo iniciarse</string>
|
<string name="startup_failed_notification_title">Briar no pudo iniciarse</string>
|
||||||
<string name="startup_failed_notification_text">Toca para más información.</string>
|
<string name="startup_failed_notification_text">Toca para más información.</string>
|
||||||
<string name="startup_failed_activity_title">Fallo al iniciar Briar</string>
|
<string name="startup_failed_activity_title">Fallo al iniciar Briar</string>
|
||||||
<string name="startup_failed_db_error">Por alguna razón, la base de datos de Briar se ha dañada irreparablemente. Tu cuenta, tus datos y tus contactos se han perdido. Lamentablemente, necesitas reinstalar Briar y configurar una nueva cuenta. Puedes hacerlo seleccionando «He olvidado mi contraseña» al arrancar Briar.</string>
|
|
||||||
<string name="startup_failed_data_too_old_error">Tu cuenta se creó en una versión antigua de esta apli y no puede abrirse con la versión actual. Debes instalar la versión anterior o eliminar tu cuenta eligiendo «He olvidado mi contraseña» al arrancar Briar.</string>
|
|
||||||
<string name="startup_failed_data_too_new_error">La versión de esta apli es demasiado antigua. Por favor, actualiza a la última versión y prueba de nuevo.</string>
|
<string name="startup_failed_data_too_new_error">La versión de esta apli es demasiado antigua. Por favor, actualiza a la última versión y prueba de nuevo.</string>
|
||||||
<string name="startup_failed_service_error">Briar no pudo iniciar un complemento necesario. Reinstalar Briar suele solucionar el problema. Sin embargo, ten en cuenta que perderás tu cuenta y todos los datos asociados ya que Briar no almacena esta información en ningún servidor central.</string>
|
<string name="startup_failed_service_error">Briar no pudo iniciar un complemento necesario. Reinstalar Briar suele solucionar el problema. Sin embargo, ten en cuenta que perderás tu cuenta y todos los datos asociados ya que Briar no almacena esta información en ningún servidor central.</string>
|
||||||
<plurals name="expiry_warning">
|
<plurals name="expiry_warning">
|
||||||
@@ -43,6 +41,8 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="expiry_update">La fecha de caducidad de las pruebas se ha ampliado. Su cuenta expirará en %d días.</string>
|
<string name="expiry_update">La fecha de caducidad de las pruebas se ha ampliado. Su cuenta expirará en %d días.</string>
|
||||||
<string name="expiry_date_reached">Esta versión ha caducado.\n¡Gracias por probarla!</string>
|
<string name="expiry_date_reached">Esta versión ha caducado.\n¡Gracias por probarla!</string>
|
||||||
|
<string name="startup_open_database">Descifrando la base de datos...</string>
|
||||||
|
<string name="startup_migrate_database">Actualizando la base de datos...</string>
|
||||||
<!--Navigation Drawer-->
|
<!--Navigation Drawer-->
|
||||||
<string name="nav_drawer_open_description">Abrir el panel de navegación</string>
|
<string name="nav_drawer_open_description">Abrir el panel de navegación</string>
|
||||||
<string name="nav_drawer_close_description">Cierra el panel de navegación</string>
|
<string name="nav_drawer_close_description">Cierra el panel de navegación</string>
|
||||||
@@ -99,9 +99,7 @@
|
|||||||
<string name="help">Ayuda</string>
|
<string name="help">Ayuda</string>
|
||||||
<string name="sorry">Disculpe</string>
|
<string name="sorry">Disculpe</string>
|
||||||
<!--Contacts and Private Conversations-->
|
<!--Contacts and Private Conversations-->
|
||||||
<string name="no_contacts">Parece que eres nuevo por aquí y no tienes aún contactos.\n\nPulsa el signo + en la parte superior y sigue las instrucciones para añadir amigos a tu lista.\n\nPor favor, recuerda: sólo puedes añadir nuevos contactos cara a cara para evitar que nadie suplante tu identidad o lea tus mensajes en el futuro. </string>
|
|
||||||
<string name="date_no_private_messages">Sin mensajes.</string>
|
<string name="date_no_private_messages">Sin mensajes.</string>
|
||||||
<string name="no_private_messages">Esta es la vista de conversación.\n\nParece que aún no hay ninguna.\n\nPulsa el campo de texto en la parte inferior para empezar la conversación.</string>
|
|
||||||
<string name="message_hint">Escribe un mensaje</string>
|
<string name="message_hint">Escribe un mensaje</string>
|
||||||
<string name="delete_contact">Eliminar contacto</string>
|
<string name="delete_contact">Eliminar contacto</string>
|
||||||
<string name="dialog_title_delete_contact">Confirmar eliminación de contacto</string>
|
<string name="dialog_title_delete_contact">Confirmar eliminación de contacto</string>
|
||||||
@@ -149,7 +147,6 @@
|
|||||||
<item quantity="other">%d nuevos contactos añadido</item>
|
<item quantity="other">%d nuevos contactos añadido</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Aún no participas en ningún grupo.\n\nCrea un grupo pulsando en el signo + arriba o pide a tus contactos que te inviten a uno de sus grupos.</string>
|
|
||||||
<string name="groups_created_by">Creado por %s</string>
|
<string name="groups_created_by">Creado por %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d mensaje</item>
|
<item quantity="one">%d mensaje</item>
|
||||||
@@ -202,12 +199,11 @@
|
|||||||
<string name="groups_reveal_visible_revealed_by_contact">Las relaciones entre los contactos son visibles al grupo (las reveló %s)</string>
|
<string name="groups_reveal_visible_revealed_by_contact">Las relaciones entre los contactos son visibles al grupo (las reveló %s)</string>
|
||||||
<string name="groups_reveal_invisible">Las relaciones entre los contactos no son visibles al grupo</string>
|
<string name="groups_reveal_invisible">Las relaciones entre los contactos no son visibles al grupo</string>
|
||||||
<!--Forums-->
|
<!--Forums-->
|
||||||
<string name="no_forums">No tienes ningún foro aún.\n\n¿Por qué no creas uno pulsando el signo + de la parte superior?\n\nTambién puedes pedirle a tus contactos que compartan foros contigo.</string>
|
|
||||||
<string name="create_forum_title">Crear foro</string>
|
<string name="create_forum_title">Crear foro</string>
|
||||||
<string name="choose_forum_hint">Elige un nombre para el foro</string>
|
<string name="choose_forum_hint">Elige un nombre para el foro</string>
|
||||||
<string name="create_forum_button">Crear foro</string>
|
<string name="create_forum_button">Crear foro</string>
|
||||||
<string name="forum_created_toast">Foro creado</string>
|
<string name="forum_created_toast">Foro creado</string>
|
||||||
<string name="no_forum_posts">Este foro está vacío.\n\nUsa el signo del lápiz de la parte superior para redactar una primera publicación.\n\n¿No te sientes un poco solo aquí? ¡Comparte este foro con más contactos!</string>
|
<string name="no_forum_posts">No hay publicaciones para mostrar</string>
|
||||||
<string name="no_posts">Sin publicaciones</string>
|
<string name="no_posts">Sin publicaciones</string>
|
||||||
<plurals name="posts">
|
<plurals name="posts">
|
||||||
<item quantity="one">%d publicación</item>
|
<item quantity="one">%d publicación</item>
|
||||||
@@ -219,23 +215,17 @@
|
|||||||
<string name="btn_reply">Responder</string>
|
<string name="btn_reply">Responder</string>
|
||||||
<string name="forum_leave">Abandonar foro</string>
|
<string name="forum_leave">Abandonar foro</string>
|
||||||
<string name="dialog_title_leave_forum">Confirmación abandono del foro</string>
|
<string name="dialog_title_leave_forum">Confirmación abandono del foro</string>
|
||||||
<string name="dialog_message_leave_forum">¿Seguro que quieres abandonar el foro? Puede que los contactos que invitaste al foro dejen de recibir actualizaciones del mismo.</string>
|
|
||||||
<string name="dialog_button_leave">Abandonar</string>
|
<string name="dialog_button_leave">Abandonar</string>
|
||||||
<string name="forum_left_toast">Foro abandonado</string>
|
|
||||||
<!--Forum Sharing-->
|
<!--Forum Sharing-->
|
||||||
<string name="forum_share_button">Compartir foro</string>
|
<string name="forum_share_button">Compartir foro</string>
|
||||||
<string name="contacts_selected">Contactos seleccionados</string>
|
<string name="contacts_selected">Contactos seleccionados</string>
|
||||||
<string name="activity_share_toolbar_header">Elige contactos</string>
|
<string name="activity_share_toolbar_header">Elige contactos</string>
|
||||||
<string name="no_contacts_selector">Parece que eres nuevo aquí y no tienes contactos aún.\n\nPor favor, vuelve cuando hayas añadido tu primer contacto.</string>
|
|
||||||
<string name="forum_shared_snackbar">Foro compartido con los contactos seleccionados</string>
|
<string name="forum_shared_snackbar">Foro compartido con los contactos seleccionados</string>
|
||||||
<string name="forum_share_message">Añade un mensaje (opcional)</string>
|
<string name="forum_share_message">Añade un mensaje (opcional)</string>
|
||||||
<string name="forum_share_error">Hubo un error compartiendo este foro.</string>
|
<string name="forum_share_error">Hubo un error compartiendo este foro.</string>
|
||||||
<string name="forum_invitation_received">%1$s ha compartido el foro \"%2$s\" contigo.</string>
|
<string name="forum_invitation_received">%1$s ha compartido el foro \"%2$s\" contigo.</string>
|
||||||
<string name="forum_invitation_sent">Has compartido el foro \"%1$s\" con %2$s.</string>
|
<string name="forum_invitation_sent">Has compartido el foro \"%1$s\" con %2$s.</string>
|
||||||
<string name="forum_invitations_title">Invitaciones a foros</string>
|
<string name="forum_invitations_title">Invitaciones a foros</string>
|
||||||
<string name="forum_invitation_exists">Ya aceptaste la invitación a este foro. Aceptar más invitaciones aumentará y fortalecerá la comunicación con el foro.</string>
|
|
||||||
<string name="forum_joined_toast">Te uniste al foro</string>
|
|
||||||
<string name="forum_declined_toast">Invitación al foro rechazada</string>
|
|
||||||
<string name="shared_by_format">Compartido por %s</string>
|
<string name="shared_by_format">Compartido por %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Ya se está compartiendo</string>
|
<string name="forum_invitation_already_sharing">Ya se está compartiendo</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Aceptaste la invitación al foro de %s.</string>
|
<string name="forum_invitation_response_accepted_sent">Aceptaste la invitación al foro de %s.</string>
|
||||||
@@ -251,19 +241,15 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="nobody">Nadie</string>
|
<string name="nobody">Nadie</string>
|
||||||
<!--Blogs-->
|
<!--Blogs-->
|
||||||
<string name="blogs_other_blog_empty_state">Este blog está vacío.\n\nO el autor no ha escrito aún, o la persona que compartió este blog contigo necesita conectarse para que se puedan sincronizar los artículos.</string>
|
<string name="blogs_other_blog_empty_state">No hay publicaciones para mostrar</string>
|
||||||
<string name="read_more">leer más</string>
|
<string name="read_more">leer más</string>
|
||||||
<string name="blogs_write_blog_post">Escribir artículo del blog</string>
|
<string name="blogs_write_blog_post">Escribir artículo del blog</string>
|
||||||
<string name="blogs_write_blog_post_body_hint">Escribe tu artículo aquí</string>
|
|
||||||
<string name="blogs_publish_blog_post">Publicar</string>
|
<string name="blogs_publish_blog_post">Publicar</string>
|
||||||
<string name="blogs_blog_post_created">Creado artículo del blog</string>
|
<string name="blogs_blog_post_created">Creado artículo del blog</string>
|
||||||
<string name="blogs_blog_post_received">Recibido nuevo artículo del blog</string>
|
<string name="blogs_blog_post_received">Recibido nuevo artículo del blog</string>
|
||||||
<string name="blogs_blog_post_scroll_to">Desplazarse hasta</string>
|
<string name="blogs_blog_post_scroll_to">Desplazarse hasta</string>
|
||||||
<string name="blogs_feed_empty_state">Esta es la lista global de entradas de blogs.\n\nParece que nadie ha publicado nada todavía.\n\nSé el primero: pulsa el signo del lápiz para escribir una nueva entrada de blog.</string>
|
|
||||||
<string name="blogs_remove_blog">Eliminar blog</string>
|
<string name="blogs_remove_blog">Eliminar blog</string>
|
||||||
<string name="blogs_remove_blog_dialog_message">¿Seguro que quieres eliminar este blog y todos sus artículos?\nTen en cuenta que no se eliminará el blog de los dispositivos de otras personas.</string>
|
<string name="blogs_remove_blog_ok">Eliminar</string>
|
||||||
<string name="blogs_remove_blog_ok">Eliminar blog</string>
|
|
||||||
<string name="blogs_blog_removed">Blog eliminado</string>
|
|
||||||
<string name="blogs_reblog_comment_hint">Añade un comentario (opcional)</string>
|
<string name="blogs_reblog_comment_hint">Añade un comentario (opcional)</string>
|
||||||
<string name="blogs_reblog_button">Rebloguear</string>
|
<string name="blogs_reblog_button">Rebloguear</string>
|
||||||
<!--Blog Sharing-->
|
<!--Blog Sharing-->
|
||||||
@@ -278,8 +264,6 @@
|
|||||||
<string name="blogs_sharing_invitation_received">%1$s ha compartido el blog \"%2$s\" contigo.</string>
|
<string name="blogs_sharing_invitation_received">%1$s ha compartido el blog \"%2$s\" contigo.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Has compartido el blog \"%1$s\" con %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Has compartido el blog \"%1$s\" con %2$s.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Invitaciones a blogs</string>
|
<string name="blogs_sharing_invitations_title">Invitaciones a blogs</string>
|
||||||
<string name="blogs_sharing_joined_toast">Suscrito al blog</string>
|
|
||||||
<string name="blogs_sharing_declined_toast">Rechazada invitación al blog</string>
|
|
||||||
<string name="sharing_status_blog">Cualquiera que se suscriba a un blog puede compartirlo con sus contactos. Estás compartiendo este blog con contactos listados a continuación. Puede haber otros suscriptores que no puedes ver.</string>
|
<string name="sharing_status_blog">Cualquiera que se suscriba a un blog puede compartirlo con sus contactos. Estás compartiendo este blog con contactos listados a continuación. Puede haber otros suscriptores que no puedes ver.</string>
|
||||||
<!--RSS Feeds-->
|
<!--RSS Feeds-->
|
||||||
<string name="blogs_rss_feeds_import">Importar canal RSS</string>
|
<string name="blogs_rss_feeds_import">Importar 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">Última actualización:</string>
|
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string>
|
||||||
<string name="blogs_rss_remove_feed">Eliminar canal RSS</string>
|
<string name="blogs_rss_remove_feed">Eliminar canal RSS</string>
|
||||||
<string name="blogs_rss_remove_feed_dialog_message">¿Seguro que quieres borrar este canal RSS y todas sus publicaciones?\nNo se eliminará ningún artículo que hayas compartido de los dispositivos de otras personas.</string>
|
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
||||||
<string name="blogs_rss_remove_feed_ok">Eliminar canal RSS</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">¡El canal no pudo ser eliminado!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">¡El canal no pudo ser eliminado!</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">No has importado ningún canal RSS.\n\n¿Por qué no pulsas el signo más de la esquina superior derecha para añadir el primero?</string>
|
|
||||||
<string name="blogs_rss_feeds_manage_error">Hubo un problema cargando tus canales RSS. Por favor, prueba más tarde.</string>
|
<string name="blogs_rss_feeds_manage_error">Hubo un problema cargando tus canales RSS. Por favor, prueba más tarde.</string>
|
||||||
<!--Settings Network-->
|
<!--Settings Network-->
|
||||||
<string name="network_settings_title">Redes</string>
|
<string name="network_settings_title">Redes</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user