Compare commits

..

3 Commits

Author SHA1 Message Date
akwizgran
f25b16e680 Listen for wifi AP state changes.
We won't necessarily receive a connectivity change event when the wifi AP is enabled.
2018-03-26 17:19:17 +01:00
akwizgran
467fdb6468 Log active network info. 2018-03-26 11:49:03 +01:00
akwizgran
c0840dc332 Log a lot of information about the network state. 2018-03-22 17:41:07 +00:00
223 changed files with 7481 additions and 9712 deletions

View File

@@ -1,8 +1,6 @@
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'
@@ -69,68 +67,30 @@ def torBinaries = [
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea' "geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
] ]
def verifyOrDeleteBinary(name, chksum, alreadyVerified) { def downloadBinary(name) {
return tasks.create("verifyOrDeleteBinary${name}", VerifyOrDelete) { return tasks.create("downloadBinary${name}", Download) {
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"
onlyIf { onlyIfNewer true
!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, chksum, alreadyVerified)]) { dependsOn: downloadBinary(name)]) {
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 { name, checksum -> torBinaries.every { key, value ->
preBuild.dependsOn.add(verifyBinary(name, checksum)) preBuild.dependsOn.add(verifyBinary(key, value))
}
}
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()
}
} }
} }

View File

@@ -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,
scheduler, backoffFactory, appContext); backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
Arrays.asList(bluetooth, tor, lan); Arrays.asList(bluetooth, tor, lan);
@NotNullByDefault @NotNullByDefault

View File

@@ -5,84 +5,43 @@ 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.os.Bundle;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
import static android.os.Build.VERSION.SDK_INT; import static java.util.logging.Level.INFO;
import static java.util.Collections.emptyList; import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
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_ACTION =
private static final String WIFI_AP_STATE_CHANGED_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED"; "android.net.wifi.WIFI_AP_STATE_CHANGED";
private static final int WIFI_AP_STATE_ENABLED = 13;
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidLanTcpPlugin.class.getName()); Logger.getLogger(AndroidLanTcpPlugin.class.getName());
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final ConnectivityManager connectivityManager;
@Nullable
private final WifiManager wifiManager;
@Nullable @Nullable
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
private volatile SocketFactory socketFactory;
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
Backoff backoff, Context appContext, DuplexPluginCallback callback, Context appContext, DuplexPluginCallback callback, int maxLatency,
int maxLatency, int maxIdleTime) { 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
@@ -93,8 +52,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
networkStateReceiver = new NetworkStateReceiver(); networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION); filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_AP_STATE_ACTION);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
} }
@Override @Override
@@ -105,92 +65,38 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
tryToClose(socket); tryToClose(socket);
} }
@Override
protected Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
@Override
protected Collection<InetAddress> getLocalIpAddresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, use that network
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0)
return singletonList(intToInetAddress(info.getIpAddress()));
// If we're running an access point, return its address
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
return singletonList(WIFI_AP_ADDRESS);
// No suitable addresses
return emptyList();
}
private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
return InetAddress.getByAddress(ipBytes);
} catch (UnknownHostException e) {
// Should only be thrown if address has illegal length
throw new AssertionError(e);
}
}
// On API 21 and later, a socket that is not created with the wifi
// network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net);
if (info != null && info.getType() == TYPE_WIFI)
return net.getSocketFactory();
}
LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault();
}
private class NetworkStateReceiver extends BroadcastReceiver { private class NetworkStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context ctx, Intent i) { public void onReceive(Context ctx, Intent i) {
if (!running) return; if (!running) return;
if (isApEnabledEvent(i)) { if (LOG.isLoggable(INFO)) {
// The state change may be broadcast before the AP address is if (CONNECTIVITY_ACTION.equals(i.getAction())) {
// visible, so delay handling the event LOG.info("Connectivity change");
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS); Bundle extras = i.getExtras();
} else { if (extras != null) {
handleConnectivityChange(); LOG.info("Extras:");
for (String key : extras.keySet())
LOG.info("\t" + key + ": " + extras.get(key));
}
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
if (state == 13) LOG.info("Wifi AP enabled");
else LOG.info("Wifi AP state " + state);
}
logNetworkState(appContext, LOG);
} }
} Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
private void handleConnectivityChange() { NetworkInfo net = cm.getActiveNetworkInfo();
if (!running) return; if (net != null && net.getType() == TYPE_WIFI
Collection<InetAddress> addrs = getLocalIpAddresses(); && net.isConnected()) {
if (addrs.contains(WIFI_AP_ADDRESS)) { LOG.info("Connected to Wi-Fi");
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(); if (socket == null || socket.isClosed()) bind();
} else if (addrs.isEmpty()) { } else {
LOG.info("Not connected to wifi"); LOG.info("Not connected to Wi-Fi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket); 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;
}
} }
} }

View File

@@ -11,7 +11,6 @@ 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;
@@ -28,15 +27,12 @@ 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,
ScheduledExecutorService scheduler, BackoffFactory backoffFactory, BackoffFactory backoffFactory, Context appContext) {
Context appContext) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.appContext = appContext; this.appContext = appContext;
} }
@@ -55,7 +51,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, scheduler, backoff, return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME); callback, MAX_LATENCY, MAX_IDLE_TIME);
} }
} }

View File

@@ -16,7 +16,6 @@ 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;
@@ -64,6 +63,8 @@ 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;
@@ -110,7 +111,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, connectionStatusExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
@@ -124,6 +125,7 @@ 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);
@@ -165,9 +167,7 @@ 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);
// Don't execute more than one connection status check at a time connectionStatusLock = new ReentrantLock();
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
ioExecutor, 1);
} }
@Override @Override
@@ -697,46 +697,55 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> { ioExecutor.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 {
if (!online) { connectionStatusLock.lock();
LOG.info("Disabling network, device is offline"); updateConnectionStatusLocked();
enableNetwork(false); } finally {
} else if (blocked) { connectionStatusLock.unlock();
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);
@@ -778,7 +787,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private synchronized void enableNetwork(boolean enable) { private synchronized void enableNetwork(boolean enable) {
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; circuitBuilt = false;
} }
private synchronized boolean isConnected() { private synchronized boolean isConnected() {

View File

@@ -1,18 +1,41 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
import java.io.File; import java.io.File;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.ipToString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@SuppressLint("HardwareIds")
public class AndroidUtils { public class AndroidUtils {
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later // Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
@@ -23,7 +46,7 @@ public class AndroidUtils {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static Collection<String> getSupportedArchitectures() { public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); List<String> abis = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 21) { if (SDK_INT >= 21) {
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS)); abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
} else { } else {
abis.add(Build.CPU_ABI); abis.add(Build.CPU_ABI);
@@ -67,4 +90,123 @@ public class AndroidUtils {
public static File getReportDir(Context ctx) { public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
} }
public static void logNetworkState(Context ctx, Logger logger) {
if (!logger.isLoggable(INFO)) return;
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
if (o == null) throw new AssertionError();
ConnectivityManager cm = (ConnectivityManager) o;
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
if (o == null) throw new AssertionError();
WifiManager wm = (WifiManager) o;
StringBuilder s = new StringBuilder();
logWifiInfo(s, wm.getConnectionInfo());
logNetworkInfo(s, cm.getActiveNetworkInfo(), true);
if (SDK_INT >= 21) {
for (Network network : cm.getAllNetworks())
logNetworkInfo(s, cm.getNetworkInfo(network), false);
} else {
for (NetworkInfo info : cm.getAllNetworkInfo())
logNetworkInfo(s, info, false);
}
try {
for (NetworkInterface iface : list(getNetworkInterfaces()))
logNetworkInterface(s, iface);
} catch (SocketException e) {
logger.log(WARNING, e.toString(), e);
}
logger.log(INFO, s.toString());
}
private static void logWifiInfo(StringBuilder s, @Nullable WifiInfo info) {
if (info == null) {
s.append("Wifi info: null\n");
return;
}
s.append("Wifi info:\n");
s.append("\tSSID: ").append(info.getSSID()).append("\n");
s.append("\tBSSID: ").append(info.getBSSID()).append("\n");
s.append("\tMAC address: ").append(info.getMacAddress()).append("\n");
s.append("\tIP address: ")
.append(ipToString(info.getIpAddress())).append("\n");
s.append("\tSupplicant state: ")
.append(info.getSupplicantState()).append("\n");
s.append("\tNetwork ID: ").append(info.getNetworkId()).append("\n");
s.append("\tLink speed: ").append(info.getLinkSpeed()).append("\n");
s.append("\tRSSI: ").append(info.getRssi()).append("\n");
if (info.getHiddenSSID()) s.append("\tHidden SSID\n");
if (SDK_INT >= 21)
s.append("\tFrequency: ").append(info.getFrequency()).append("\n");
}
private static void logNetworkInfo(StringBuilder s,
@Nullable NetworkInfo info, boolean active) {
if (info == null) {
if (active) s.append("Active network info: null\n");
else s.append("Network info: null\n");
return;
}
if (active) s.append("Active network info:\n");
else s.append("Network info:\n");
s.append("\tType: ").append(info.getTypeName())
.append(" (").append(info.getType()).append(")\n");
s.append("\tSubtype: ").append(info.getSubtypeName())
.append(" (").append(info.getSubtype()).append(")\n");
s.append("\tState: ").append(info.getState()).append("\n");
s.append("\tDetailed state: ")
.append(info.getDetailedState()).append("\n");
s.append("\tReason: ").append(info.getReason()).append("\n");
s.append("\tExtra info: ").append(info.getExtraInfo()).append("\n");
if (info.isAvailable()) s.append("\tAvailable\n");
if (info.isConnected()) s.append("\tConnected\n");
if (info.isConnectedOrConnecting())
s.append("\tConnected or connecting\n");
if (info.isFailover()) s.append("\tFailover\n");
if (info.isRoaming()) s.append("\tRoaming\n");
}
private static void logNetworkInterface(StringBuilder s,
NetworkInterface iface) throws SocketException {
s.append("Network interface:\n");
s.append("\tName: ").append(iface.getName()).append("\n");
s.append("\tDisplay name: ")
.append(iface.getDisplayName()).append("\n");
s.append("\tHardware address: ")
.append(hexOrNull(iface.getHardwareAddress())).append("\n");
if (iface.isLoopback()) s.append("\tLoopback\n");
if (iface.isPointToPoint()) s.append("\tPoint-to-point\n");
if (iface.isVirtual()) s.append("\tVirtual\n");
if (iface.isUp()) s.append("\tUp\n");
if (SDK_INT >= 19)
s.append("\tIndex: ").append(iface.getIndex()).append("\n");
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
s.append("\tInterface address:\n");
logInetAddress(s, addr.getAddress());
s.append("\t\tPrefix length: ")
.append(addr.getNetworkPrefixLength()).append("\n");
}
}
private static void logInetAddress(StringBuilder s, InetAddress addr) {
s.append("\t\tAddress: ")
.append(hexOrNull(addr.getAddress())).append("\n");
s.append("\t\tHost address: ")
.append(addr.getHostAddress()).append("\n");
if (addr.isLoopbackAddress()) s.append("\t\tLoopback\n");
if (addr.isLinkLocalAddress()) s.append("\t\tLink-local\n");
if (addr.isSiteLocalAddress()) s.append("\t\tSite-local\n");
if (addr.isAnyLocalAddress()) s.append("\t\tAny local (wildcard)\n");
if (addr.isMCNodeLocal()) s.append("\t\tMulticast node-local\n");
if (addr.isMCLinkLocal()) s.append("\t\tMulticast link-local\n");
if (addr.isMCSiteLocal()) s.append("\t\tMulticast site-local\n");
if (addr.isMCOrgLocal()) s.append("\t\tMulticast org-local\n");
if (addr.isMCGlobal()) s.append("\t\tMulticast global\n");
}
@Nullable
private static String hexOrNull(@Nullable byte[] b) {
return b == null ? null : toHexString(b);
}
} }

View File

@@ -7,8 +7,6 @@ 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;
@@ -90,10 +88,6 @@ 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;
@@ -105,15 +99,8 @@ 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(byte[] signature, String label, BdfList signed, void verifySignature(String label, byte[] sig, byte[] publicKey,
byte[] publicKey) throws FormatException, GeneralSecurityException; BdfList signed) 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;
} }

View File

@@ -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 the local device. * the scope of a single node.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -5,7 +5,6 @@ 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;
@@ -14,28 +13,23 @@ import java.util.Collection;
public interface ContactManager { public interface ContactManager {
/** /**
* Registers a hook to be called whenever a contact is added or removed. * Registers a hook to be called whenever a contact is added.
* This method should be called before
* {@link LifecycleManager#startServices(String)}.
*/ */
void registerContactHook(ContactHook hook); void registerAddContactHook(AddContactHook hook);
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Registers a hook to be called whenever a contact is removed.
* derives and stores transport keys for each transport, and returns an ID */
* for the contact. void registerRemoveContactHook(RemoveContactHook hook);
/**
* Stores a contact within the given transaction associated with the given
* local and remote pseudonyms, and returns an ID for the contact.
*/ */
ContactId addContact(Transaction txn, Author remote, AuthorId local, 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.
@@ -100,10 +94,11 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId) boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException; throws DbException;
interface ContactHook { interface AddContactHook {
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;
} }
} }

View File

@@ -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 verifySignature(byte[] signature, String label, byte[] signed, boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] publicKey) throws GeneralSecurityException; byte[] signature) 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,18 +91,6 @@ 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

View File

@@ -16,10 +16,4 @@ 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;
} }

View File

@@ -14,10 +14,9 @@ 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, boolean active); long rotationPeriod, boolean alice);
/** /**
* Rotates the given transport keys to the given rotation period. If the * Rotates the given transport keys to the given rotation period. If the

View File

@@ -34,7 +34,7 @@ public class BdfDictionary extends TreeMap<String, Object> {
super(); super();
} }
public BdfDictionary(Map<String, ?> m) { public BdfDictionary(Map<String, Object> m) {
super(m); super(m);
} }

View File

@@ -18,8 +18,7 @@ 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.transport.KeySet; import org.briarproject.bramble.api.sync.ValidationManager;
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;
@@ -104,17 +103,10 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, optionally binding them to the given * Stores transport keys for a newly added contact.
* contact, and returns a key set ID.
*/ */
KeySetId addTransportKeys(Transaction txn, @Nullable ContactId c, void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
TransportKeys k) throws DbException; 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
@@ -347,8 +339,12 @@ 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.
* For missing dependencies and dependencies in other groups, the state * Missing dependencies have the state
* {@link State UNKNOWN} is returned. * {@link ValidationManager.State UNKNOWN}.
* Dependencies in other groups have the state
* {@link ValidationManager.State INVALID}.
* Note that these states are not set on the dependencies themselves; the
* returned states should only be taken in the context of the given message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -356,9 +352,9 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the IDs and states of all dependents of the given message. * Returns all IDs of messages that depend on the given message.
* Dependents in other groups are not returned. If the given message is * Messages in other groups that declare a dependency on the given message
* missing, no dependents are returned. * will be returned even though such dependencies are invalid.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -403,14 +399,15 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<KeySet> getTransportKeys(Transaction txn, TransportId t) Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
throws DbException; TransportId t) throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given contact and
* transport in the given rotation period .
*/ */
void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k) void incrementStreamCounter(Transaction txn, ContactId c, TransportId t,
throws DbException; long rotationPeriod) throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
@@ -480,12 +477,6 @@ 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.
*/ */
@@ -521,21 +512,15 @@ public interface DatabaseComponent {
Collection<MessageId> dependencies) throws DbException; Collection<MessageId> dependencies) throws DbException;
/** /**
* Sets the reordering window for the given key set and transport in the * Sets the reordering window for the given contact and transport in the
* given rotation period. * given rotation period.
*/ */
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t, void setReorderingWindow(Transaction txn, ContactId c, TransportId t,
long rotationPeriod, long base, byte[] bitmap) throws DbException; 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, Collection<KeySet> keys) void updateTransportKeys(Transaction txn,
throws DbException; Map<ContactId, TransportKeys> keys) throws DbException;
} }

View File

@@ -1,23 +1,22 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.util.StringUtils; import java.nio.charset.Charset;
/** /**
* Type-safe wrapper for a namespaced string that uniquely identifies a * Type-safe wrapper for a string that uniquely identifies a transport plugin.
* transport plugin.
*/ */
public class TransportId { public class TransportId {
/** /**
* The maximum length of a transport identifier in UTF-8 bytes. * The maximum length of transport identifier in UTF-8 bytes.
*/ */
public static int MAX_TRANSPORT_ID_LENGTH = 100; public static int MAX_TRANSPORT_ID_LENGTH = 64;
private final String id; private final String id;
public TransportId(String id) { public TransportId(String id) {
int length = StringUtils.toUtf8(id).length; byte[] b = id.getBytes(Charset.forName("UTF-8"));
if (length == 0 || length > MAX_TRANSPORT_ID_LENGTH) if (b.length == 0 || b.length > MAX_TRANSPORT_ID_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
this.id = id; this.id = id;
} }

View File

@@ -1,29 +1,19 @@
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;
/** /**
* Type-safe wrapper for a namespaced string that uniquely identifies a sync * Wrapper for a name-spaced string that uniquely identifies a sync client.
* 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;
} }

View File

@@ -10,11 +10,6 @@ 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;

View File

@@ -5,11 +5,6 @@ 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;

View File

@@ -16,13 +16,7 @@ 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 ID_LABEL = "org.briarproject.bramble/MESSAGE_ID"; public static final String 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);

View File

@@ -6,8 +6,6 @@ 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;
/** /**
@@ -18,51 +16,13 @@ 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 a set of transport keys for communicating with the contact over * stores transport keys for communicating with the contact.
* 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

View File

@@ -1,51 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* A set of transport keys for communicating with a contact. If the keys have
* not yet been bound to a contact, {@link #getContactId()}} returns null.
*/
@Immutable
@NotNullByDefault
public class KeySet {
private final KeySetId keySetId;
@Nullable
private final ContactId contactId;
private final TransportKeys transportKeys;
public KeySet(KeySetId keySetId, @Nullable ContactId contactId,
TransportKeys transportKeys) {
this.keySetId = keySetId;
this.contactId = contactId;
this.transportKeys = transportKeys;
}
public KeySetId getKeySetId() {
return keySetId;
}
@Nullable
public ContactId getContactId() {
return contactId;
}
public TransportKeys getTransportKeys() {
return transportKeys;
}
@Override
public int hashCode() {
return keySetId.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof KeySet && keySetId.equals(((KeySet) o).keySetId);
}
}

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Type-safe wrapper for an integer that uniquely identifies a set of transport
* keys within the scope of the local device.
* <p/>
* Key sets created on a given device must have increasing identifiers.
*/
@Immutable
@NotNullByDefault
public class KeySetId {
private final int id;
public KeySetId(int id) {
this.id = id;
}
public int getInt() {
return id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object o) {
return o instanceof KeySetId && id == ((KeySetId) o).id;
}
}

View File

@@ -10,20 +10,18 @@ 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, boolean active) { long rotationPeriod) {
this(tagKey, headerKey, rotationPeriod, 0, active); this(tagKey, headerKey, rotationPeriod, 0);
} }
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey, public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
long rotationPeriod, long streamCounter, boolean active) { long rotationPeriod, long streamCounter) {
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() {
@@ -41,8 +39,4 @@ public class OutgoingKeys {
public long getStreamCounter() { public long getStreamCounter() {
return streamCounter; return streamCounter;
} }
public boolean isActive() {
return active;
}
} }

View File

@@ -146,6 +146,14 @@ public class StringUtils {
return s.toString(); return s.toString();
} }
public static String ipToString(int ip) {
int ip1 = ip & 0xFF;
int ip2 = (ip >> 8) & 0xFF;
int ip3 = (ip >> 16) & 0xFF;
int ip4 = (ip >> 24) & 0xFF;
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}
public static String getRandomString(int length) { public static String getRandomString(int length) {
char[] c = new char[length]; char[] c = new char[length];
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)

View File

@@ -5,8 +5,6 @@ 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;
@@ -18,18 +16,13 @@ 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;
@@ -61,33 +54,6 @@ 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));
} }

View File

@@ -18,8 +18,6 @@ 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;
@@ -39,8 +37,6 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.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;
@@ -328,20 +324,6 @@ class ClientHelperImpl implements ClientHelper {
} }
} }
@Override
public BdfDictionary toDictionary(TransportProperties transportProperties) {
return new BdfDictionary(transportProperties);
}
@Override
public BdfDictionary toDictionary(
Map<TransportId, TransportProperties> map) {
BdfDictionary d = new BdfDictionary();
for (Entry<TransportId, TransportProperties> e : map.entrySet())
d.put(e.getKey().getString(), new BdfDictionary(e.getValue()));
return d;
}
@Override @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);
@@ -381,10 +363,9 @@ class ClientHelperImpl implements ClientHelper {
} }
@Override @Override
public void verifySignature(byte[] signature, String label, BdfList signed, public void verifySignature(String label, byte[] sig, byte[] publicKey,
byte[] publicKey) throws FormatException, GeneralSecurityException { BdfList signed) throws FormatException, GeneralSecurityException {
if (!crypto.verifySignature(signature, label, toByteArray(signed), if (!crypto.verify(label, toByteArray(signed), publicKey, sig)) {
publicKey)) {
throw new GeneralSecurityException("Invalid signature"); throw new GeneralSecurityException("Invalid signature");
} }
} }
@@ -401,33 +382,4 @@ 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;
}
} }

View File

@@ -251,8 +251,7 @@ 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.verifySignature(sig, SIGNING_LABEL_EXCHANGE, nonce, if (!crypto.verify(SIGNING_LABEL_EXCHANGE, nonce, publicKey, sig)) {
publicKey)) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Invalid signature"); LOG.info("Invalid signature");
throw new GeneralSecurityException(); throw new GeneralSecurityException();

View File

@@ -27,37 +27,36 @@ class ContactManagerImpl implements ContactManager {
private final DatabaseComponent db; private final DatabaseComponent db;
private final KeyManager keyManager; private final KeyManager keyManager;
private final List<ContactHook> hooks; private final List<AddContactHook> addHooks;
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;
hooks = new CopyOnWriteArrayList<>(); addHooks = new CopyOnWriteArrayList<>();
removeHooks = new CopyOnWriteArrayList<>();
} }
@Override @Override
public void registerContactHook(ContactHook hook) { public void registerAddContactHook(AddContactHook hook) {
hooks.add(hook); addHooks.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 (ContactHook hook : hooks) hook.addingContact(txn, contact); for (AddContactHook hook : addHooks)
return c; hook.addingContact(txn, contact);
}
@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;
} }
@@ -157,7 +156,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; boolean exists = false;
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
exists = contactExists(txn, remoteAuthorId, localAuthorId); exists = contactExists(txn, remoteAuthorId, localAuthorId);
@@ -172,7 +171,8 @@ 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 (ContactHook hook : hooks) hook.removingContact(txn, contact); for (RemoveContactHook hook : removeHooks)
hook.removingContact(txn, contact);
db.removeContact(txn, c); db.removeContact(txn, c);
} }

View File

@@ -205,12 +205,12 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public boolean verifySignature(byte[] signature, String label, public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signed, byte[] publicKey) throws GeneralSecurityException { byte[] signature) 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, signed); updateSignature(sig, label, signedData);
return sig.verify(signature); return sig.verify(signature);
} }
@@ -262,17 +262,6 @@ 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();

View File

@@ -36,8 +36,7 @@ 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);
@@ -58,7 +57,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, active); rotationPeriod);
// 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);
} }
@@ -72,7 +71,6 @@ 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;
@@ -82,7 +80,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, active); outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
} }
// 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,

View File

@@ -21,8 +21,6 @@ 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;
@@ -107,11 +105,10 @@ interface Database<T> {
@Nullable ContactId sender) throws DbException; @Nullable ContactId sender) throws DbException;
/** /**
* Adds a dependency between two messages, where the dependent message is * Adds a dependency between two messages in the given group.
* in the given state.
*/ */
void addMessageDependency(T txn, Message dependent, MessageId dependency, void addMessageDependency(T txn, GroupId g, MessageId dependent,
State dependentState) throws DbException; MessageId dependency) throws DbException;
/** /**
* Records that a message has been offered by the given contact. * Records that a message has been offered by the given contact.
@@ -125,16 +122,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, optionally binding them to the given * Stores transport keys for a newly added contact.
* contact, and returns a key set ID.
*/ */
KeySetId addTransportKeys(T txn, @Nullable ContactId c, TransportKeys k) void addTransportKeys(T txn, 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;
/** /**
@@ -302,8 +292,10 @@ 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.
* For missing dependencies and dependencies in other groups, the state * Missing dependencies have the state {@link State UNKNOWN}.
* {@link State UNKNOWN} is returned. * Dependencies in other groups have the state {@link State INVALID}.
* Note that these states are not set on the dependencies themselves; the
* returned states should only be taken in the context of the given message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -311,9 +303,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs and states of all dependents of the given message. * Returns all IDs and states of all dependents of the given message.
* Dependents in other groups are not returned. If the given message is * Messages in other groups that declare a dependency on the given message
* missing, no dependents are returned. * will be returned even though such dependencies are invalid.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -495,14 +487,15 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<KeySet> getTransportKeys(T txn, TransportId t) Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given contact and
* transport in the given rotation period.
*/ */
void incrementStreamCounter(T txn, TransportId t, KeySetId k) void incrementStreamCounter(T txn, ContactId c, TransportId t,
throws DbException; long rotationPeriod) 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
@@ -592,12 +585,6 @@ 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.
@@ -633,18 +620,12 @@ 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 key set and transport in the * Sets the reordering window for the given contact and transport in the
* given rotation period. * given rotation period.
*/ */
void setReorderingWindow(T txn, KeySetId k, TransportId t, void setReorderingWindow(T txn, ContactId c, TransportId t,
long rotationPeriod, long base, byte[] bitmap) throws DbException; 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
@@ -656,5 +637,6 @@ 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, Collection<KeySet> keys) throws DbException; void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
throws DbException;
} }

View File

@@ -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,27 +234,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public KeySetId addTransportKeys(Transaction transaction, public void addTransportKeys(Transaction transaction, ContactId c,
@Nullable ContactId c, TransportKeys k) throws DbException { 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, t)) if (!db.containsTransport(txn, k.getTransportId()))
throw new NoSuchTransportException(); throw new NoSuchTransportException();
db.bindTransportKeys(txn, c, t, k); db.addTransportKeys(txn, c, k);
} }
@Override @Override
@@ -598,8 +586,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<KeySet> getTransportKeys(Transaction transaction, public Map<ContactId, TransportKeys> getTransportKeys(
TransportId t) throws DbException { Transaction transaction, 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();
@@ -607,13 +595,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, ContactId c,
KeySetId k) throws DbException { TransportId t, long rotationPeriod) 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, t, k); db.incrementStreamCounter(txn, c, t, rotationPeriod);
} }
@Override @Override
@@ -775,7 +765,6 @@ 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);
} }
@@ -789,16 +778,6 @@ 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 {
@@ -871,42 +850,38 @@ 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, dependency, dependentState); db.addMessageDependency(txn, dependent.getGroupId(),
dependent.getId(), dependency);
} }
} }
@Override @Override
public void setReorderingWindow(Transaction transaction, KeySetId k, public void setReorderingWindow(Transaction transaction, ContactId c,
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, k, t, rotationPeriod, base, bitmap); db.setReorderingWindow(txn, c, 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,
Collection<KeySet> keys) throws DbException { Map<ContactId, TransportKeys> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
Collection<KeySet> filtered = new ArrayList<>(); Map<ContactId, TransportKeys> filtered = new HashMap<>();
for (KeySet ks : keys) { for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
TransportId t = ks.getTransportKeys().getTransportId(); ContactId c = e.getKey();
if (db.containsTransport(txn, t)) filtered.add(ks); TransportKeys k = e.getValue();
if (db.containsContact(txn, c)
&& db.containsTransport(txn, k.getTransportId())) {
filtered.put(c, k);
}
} }
db.updateTransportKeys(txn, filtered); db.updateTransportKeys(txn, filtered);
} }

View File

@@ -25,8 +25,6 @@ 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;
@@ -52,7 +50,6 @@ 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;
@@ -60,6 +57,7 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.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;
@@ -74,7 +72,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 = 36; static final int CODE_SCHEMA_VERSION = 35;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -172,10 +170,6 @@ 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,"
@@ -225,44 +219,37 @@ 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"
+ " (transportId _STRING NOT NULL," + " (contactId INT NOT NULL,"
+ " keySetId INT NOT NULL," + " transportId _STRING 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 (transportId, keySetId, rotationPeriod)," + " PRIMARY KEY (contactId, transportId, rotationPeriod),"
+ " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (keySetId)"
+ " REFERENCES outgoingKeys (keySetId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_OUTGOING_KEYS =
"CREATE TABLE outgoingKeys"
+ " (contactId INT NOT NULL,"
+ " transportId _STRING NOT NULL,"
+ " rotationPeriod BIGINT NOT NULL,"
+ " tagKey _SECRET NOT NULL,"
+ " headerKey _SECRET NOT NULL,"
+ " stream BIGINT NOT NULL,"
+ " PRIMARY KEY (contactId, transportId),"
+ " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String INDEX_CONTACTS_BY_AUTHOR_ID = private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
@@ -277,10 +264,6 @@ 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)";
@@ -424,8 +407,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_OUTGOING_KEYS));
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS)); s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s);
@@ -440,7 +423,6 @@ 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();
@@ -733,17 +715,6 @@ 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);
@@ -813,42 +784,21 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void addMessageDependency(Connection txn, Message dependent, public void addMessageDependency(Connection txn, GroupId g,
MessageId dependency, State dependentState) throws DbException { MessageId dependent, MessageId dependency) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null;
try { try {
// Get state of dependency if present and in same group as dependent String sql = "INSERT INTO messageDependencies"
String sql = "SELECT state FROM messages" + " (groupId, messageId, dependencyId)"
+ " WHERE messageId = ? AND groupId = ?"; + " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, dependency.getBytes()); ps.setBytes(1, g.getBytes());
ps.setBytes(2, dependent.getGroupId().getBytes()); ps.setBytes(2, dependent.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);
} }
@@ -874,104 +824,60 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public KeySetId addTransportKeys(Connection txn, @Nullable ContactId c, public void addTransportKeys(Connection txn, ContactId c, TransportKeys k)
TransportKeys k) throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null;
try { try {
// Store the outgoing keys // Store the incoming keys
String sql = "INSERT INTO outgoingKeys (contactId, transportId," String sql = "INSERT INTO incomingKeys (contactId, transportId,"
+ " rotationPeriod, tagKey, headerKey, stream, active)" + " rotationPeriod, tagKey, headerKey, base, bitmap)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
if (c == null) ps.setNull(1, INTEGER); ps.setInt(1, c.getInt());
else ps.setInt(1, c.getInt());
ps.setString(2, k.getTransportId().getString()); ps.setString(2, k.getTransportId().getString());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(3, outCurr.getRotationPeriod());
ps.setBytes(4, outCurr.getTagKey().getBytes());
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
ps.setLong(6, outCurr.getStreamCounter());
ps.setBoolean(7, outCurr.isActive());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
// Get the new (highest) key set ID
sql = "SELECT keySetId FROM outgoingKeys"
+ " ORDER BY keySetId DESC LIMIT 1";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
KeySetId keySetId = new KeySetId(rs.getInt(1));
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
// Store the incoming keys
sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId,"
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, keySetId.getInt());
if (c == null) ps.setNull(2, INTEGER);
else ps.setInt(2, c.getInt());
ps.setString(3, k.getTransportId().getString());
// Previous rotation period // Previous rotation period
IncomingKeys inPrev = k.getPreviousIncomingKeys(); IncomingKeys inPrev = k.getPreviousIncomingKeys();
ps.setLong(4, inPrev.getRotationPeriod()); ps.setLong(3, inPrev.getRotationPeriod());
ps.setBytes(5, inPrev.getTagKey().getBytes()); ps.setBytes(4, inPrev.getTagKey().getBytes());
ps.setBytes(6, inPrev.getHeaderKey().getBytes()); ps.setBytes(5, inPrev.getHeaderKey().getBytes());
ps.setLong(7, inPrev.getWindowBase()); ps.setLong(6, inPrev.getWindowBase());
ps.setBytes(8, inPrev.getWindowBitmap()); ps.setBytes(7, inPrev.getWindowBitmap());
ps.addBatch(); ps.addBatch();
// Current rotation period // Current rotation period
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
ps.setLong(4, inCurr.getRotationPeriod()); ps.setLong(3, inCurr.getRotationPeriod());
ps.setBytes(5, inCurr.getTagKey().getBytes()); ps.setBytes(4, inCurr.getTagKey().getBytes());
ps.setBytes(6, inCurr.getHeaderKey().getBytes()); ps.setBytes(5, inCurr.getHeaderKey().getBytes());
ps.setLong(7, inCurr.getWindowBase()); ps.setLong(6, inCurr.getWindowBase());
ps.setBytes(8, inCurr.getWindowBitmap()); ps.setBytes(7, inCurr.getWindowBitmap());
ps.addBatch(); ps.addBatch();
// Next rotation period // Next rotation period
IncomingKeys inNext = k.getNextIncomingKeys(); IncomingKeys inNext = k.getNextIncomingKeys();
ps.setLong(4, inNext.getRotationPeriod()); ps.setLong(3, inNext.getRotationPeriod());
ps.setBytes(5, inNext.getTagKey().getBytes()); ps.setBytes(4, inNext.getTagKey().getBytes());
ps.setBytes(6, inNext.getHeaderKey().getBytes()); ps.setBytes(5, inNext.getHeaderKey().getBytes());
ps.setLong(7, inNext.getWindowBase()); ps.setLong(6, inNext.getWindowBase());
ps.setBytes(8, inNext.getWindowBitmap()); ps.setBytes(7, inNext.getWindowBitmap());
ps.addBatch(); ps.addBatch();
int[] batchAffected = ps.executeBatch(); int[] batchAffected = ps.executeBatch();
if (batchAffected.length != 3) throw new DbStateException(); if (batchAffected.length != 3) throw new DbStateException();
for (int rows : batchAffected) for (int rows : batchAffected)
if (rows != 1) throw new DbStateException(); if (rows != 1) throw new DbStateException();
ps.close(); ps.close();
return keySetId; // Store the outgoing keys
} catch (SQLException e) { sql = "INSERT INTO outgoingKeys (contactId, transportId,"
tryToClose(rs); + " rotationPeriod, tagKey, headerKey, stream)"
tryToClose(ps); + " VALUES (?, ?, ?, ?, ?, ?)";
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 = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, k.getInt()); ps.setString(2, k.getTransportId().getString());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(3, outCurr.getRotationPeriod());
ps.setBytes(4, outCurr.getTagKey().getBytes());
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
ps.setLong(6, outCurr.getStreamCounter());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException(); if (affected != 1) 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(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
@@ -1757,9 +1663,11 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT dependencyId, dependencyState" String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
+ " FROM messageDependencies" + " FROM messageDependencies AS d"
+ " WHERE messageId = ?"; + " LEFT OUTER JOIN messages AS m"
+ " 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();
@@ -1767,8 +1675,14 @@ 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 or in a different group state = UNKNOWN; // Missing dependency
} else {
GroupId dependentGroupId = new GroupId(rs.getBytes(3));
GroupId dependencyGroupId = new GroupId(rs.getBytes(4));
if (!dependentGroupId.equals(dependencyGroupId))
state = INVALID; // Dependency in another group
}
dependencies.put(dependency, state); dependencies.put(dependency, state);
} }
rs.close(); rs.close();
@@ -1787,12 +1701,11 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
// Exclude dependencies that are missing or in a different group String sql = "SELECT d.messageId, m.state"
// from the dependent + " FROM messageDependencies AS d"
String sql = "SELECT messageId, messageState" + " JOIN messages AS m"
+ " FROM messageDependencies" + " ON d.messageId = m.messageId"
+ " 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();
@@ -2131,8 +2044,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<KeySet> getTransportKeys(Connection txn, TransportId t) public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
throws DbException { TransportId t) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2141,7 +2054,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " base, bitmap" + " base, bitmap"
+ " FROM incomingKeys" + " FROM incomingKeys"
+ " WHERE transportId = ?" + " WHERE transportId = ?"
+ " ORDER BY keySetId, rotationPeriod"; + " ORDER BY contactId, 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();
@@ -2158,34 +2071,29 @@ 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 keySetId, contactId, rotationPeriod," sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
+ " tagKey, headerKey, stream, active"
+ " FROM outgoingKeys" + " FROM outgoingKeys"
+ " WHERE transportId = ?" + " WHERE transportId = ?"
+ " ORDER BY keySetId"; + " ORDER BY contactId, 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();
Collection<KeySet> keys = new ArrayList<>(); Map<ContactId, TransportKeys> keys = new HashMap<>();
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();
KeySetId keySetId = new KeySetId(rs.getInt(1)); ContactId contactId = new ContactId(rs.getInt(1));
ContactId contactId = new ContactId(rs.getInt(2)); long rotationPeriod = rs.getLong(2);
if (rs.wasNull()) contactId = null; SecretKey tagKey = new SecretKey(rs.getBytes(3));
long rotationPeriod = rs.getLong(3); SecretKey headerKey = new SecretKey(rs.getBytes(4));
SecretKey tagKey = new SecretKey(rs.getBytes(4)); long streamCounter = rs.getLong(5);
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, active); rotationPeriod, streamCounter);
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);
TransportKeys transportKeys = new TransportKeys(t, inPrev, keys.put(contactId, new TransportKeys(t, inPrev, inCurr,
inCurr, inNext, outCurr); inNext, outCurr));
keys.add(new KeySet(keySetId, contactId, transportKeys));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2198,15 +2106,17 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void incrementStreamCounter(Connection txn, TransportId t, public void incrementStreamCounter(Connection txn, ContactId c,
KeySetId k) throws DbException { TransportId t, long rotationPeriod) 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 transportId = ? AND keySetId = ?"; + " WHERE contactId = ? AND transportId = ?"
+ " AND rotationPeriod = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, t.getString()); ps.setInt(1, c.getInt());
ps.setInt(2, k.getInt()); ps.setString(2, t.getString());
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();
@@ -2682,27 +2592,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void removeTransportKeys(Connection txn, TransportId t, KeySetId k)
throws DbException {
PreparedStatement ps = null;
try {
// Delete any existing outgoing keys - this will also remove any
// incoming keys with the same key set ID
String sql = "DELETE FROM outgoingKeys"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
ps.setInt(2, k.getInt());
int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
@Override @Override
public void resetExpiryTime(Connection txn, ContactId c, MessageId m) public void resetExpiryTime(Connection txn, ContactId c, MessageId m)
throws DbException { throws DbException {
@@ -2842,25 +2731,6 @@ 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);
@@ -2868,18 +2738,18 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void setReorderingWindow(Connection txn, KeySetId k, TransportId t, public void setReorderingWindow(Connection txn, ContactId c, TransportId t,
long rotationPeriod, long base, byte[] bitmap) throws DbException { 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 transportId = ? AND keySetId = ?" + " WHERE contactId = ? AND transportId = ?"
+ " 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.setString(3, t.getString()); ps.setInt(3, c.getInt());
ps.setInt(4, k.getInt()); ps.setString(4, t.getString());
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();
@@ -2890,25 +2760,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void setTransportKeysActive(Connection txn, TransportId t,
KeySetId k) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE outgoingKeys SET active = true"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setString(1, t.getString());
ps.setInt(2, k.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
@Override @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 {
@@ -2944,12 +2795,45 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void updateTransportKeys(Connection txn, Collection<KeySet> keys) public void updateTransportKeys(Connection txn,
throws DbException { Map<ContactId, TransportKeys> keys) throws DbException {
for (KeySet ks : keys) { PreparedStatement ps = null;
TransportKeys k = ks.getTransportKeys(); try {
removeTransportKeys(txn, k.getTransportId(), ks.getKeySetId()); // Delete any existing incoming keys
addTransportKeys(txn, ks.getContactId(), k); String sql = "DELETE FROM incomingKeys"
+ " WHERE contactId = ?"
+ " AND transportId = ?";
ps = txn.prepareStatement(sql);
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
ps.setInt(1, e.getKey().getInt());
ps.setString(2, e.getValue().getTransportId().getString());
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
if (batchAffected.length != keys.size())
throw new DbStateException();
ps.close();
// Delete any existing outgoing keys
sql = "DELETE FROM outgoingKeys"
+ " WHERE contactId = ?"
+ " AND transportId = ?";
ps = txn.prepareStatement(sql);
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
ps.setInt(1, e.getKey().getInt());
ps.setString(2, e.getValue().getTransportId().getString());
ps.addBatch();
}
batchAffected = ps.executeBatch();
if (batchAffected.length != keys.size())
throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
// Store the new keys
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
addTransportKeys(txn, e.getKey(), e.getValue());
} }
} }
} }

View File

@@ -241,11 +241,10 @@ 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))

View File

@@ -1,6 +1,5 @@
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;
@@ -48,7 +47,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, bindExecutor; protected final Executor ioExecutor;
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;
@@ -91,8 +90,6 @@ 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
@@ -113,9 +110,8 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
protected void bind() { protected void bind() {
bindExecutor.execute(() -> { ioExecutor.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 {
@@ -247,11 +243,10 @@ 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))
@@ -266,10 +261,6 @@ 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;

View File

@@ -46,7 +46,8 @@ public class PropertiesModule {
lifecycleManager.registerClient(transportPropertyManager); lifecycleManager.registerClient(transportPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, validationManager.registerIncomingMessageHook(CLIENT_ID,
transportPropertyManager); transportPropertyManager);
contactManager.registerContactHook(transportPropertyManager); contactManager.registerAddContactHook(transportPropertyManager);
contactManager.registerRemoveContactHook(transportPropertyManager);
return transportPropertyManager; return transportPropertyManager;
} }
} }

View File

@@ -5,7 +5,8 @@ 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.ContactHook; import org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
import org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.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;
@@ -39,7 +40,7 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
Client, ContactHook, IncomingMessageHook { Client, AddContactHook, RemoveContactHook, IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
@@ -347,7 +348,10 @@ 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);
return clientHelper.parseAndValidateTransportProperties(dictionary); TransportProperties p = new TransportProperties();
for (String key : dictionary.keySet())
p.put(key, dictionary.getString(key));
return p;
} }
private static class LatestUpdate { private static class LatestUpdate {

View File

@@ -15,6 +15,8 @@ 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;
@@ -40,7 +42,12 @@ 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);
clientHelper.parseAndValidateTransportProperties(dictionary); checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
for (String key : dictionary.keySet()) {
checkLength(key, 0, MAX_PROPERTY_LENGTH);
String value = dictionary.getString(key);
checkLength(value, 0, MAX_PROPERTY_LENGTH);
}
// Return the metadata // Return the metadata
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put("transportId", transportId); meta.put("transportId", transportId);

View File

@@ -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[] {FORMAT_VERSION}, byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_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);

View File

@@ -12,12 +12,10 @@ 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.Message.FORMAT_VERSION; import static org.briarproject.bramble.api.sync.MessageId.LABEL;
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.util.ByteUtils.INT_64_BYTES; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -34,14 +32,11 @@ 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[] versionBytes = new byte[] {FORMAT_VERSION}; byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
// 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[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(), byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
timeBytes, rootHash); g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash); MessageId id = new MessageId(hash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length]; byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH); System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);

View File

@@ -4,9 +4,8 @@ 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.RejectedExecutionHandler; import java.util.concurrent.Executors;
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;
@@ -26,10 +25,7 @@ public class SystemModule {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
public SystemModule() { public SystemModule() {
// Discard tasks that are submitted during shutdown scheduler = Executors.newSingleThreadScheduledExecutor();
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduler = new ScheduledThreadPoolExecutor(1, policy);
} }
@Provides @Provides

View File

@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.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;
@@ -105,67 +104,6 @@ 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 {
@@ -176,7 +114,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; StreamContext ctx = null;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
ctx = m.getStreamContext(txn, c); ctx = m.getStreamContext(txn, c);
@@ -195,7 +133,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
return null; return null;
} }
StreamContext ctx; StreamContext ctx = null;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
ctx = m.getStreamContext(txn, tag); ctx = m.getStreamContext(txn, tag);

View File

@@ -1,34 +0,0 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
public class MutableKeySet {
private final KeySetId keySetId;
@Nullable
private final ContactId contactId;
private final MutableTransportKeys transportKeys;
public MutableKeySet(KeySetId keySetId, @Nullable ContactId contactId,
MutableTransportKeys transportKeys) {
this.keySetId = keySetId;
this.contactId = contactId;
this.transportKeys = transportKeys;
}
public KeySetId getKeySetId() {
return keySetId;
}
@Nullable
public ContactId getContactId() {
return contactId;
}
public MutableTransportKeys getTransportKeys() {
return transportKeys;
}
}

View File

@@ -13,19 +13,17 @@ 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, active); streamCounter);
} }
SecretKey getTagKey() { SecretKey getTagKey() {
@@ -47,12 +45,4 @@ class MutableOutgoingKeys {
void incrementStreamCounter() { void incrementStreamCounter() {
streamCounter++; streamCounter++;
} }
boolean isActive() {
return active;
}
void activate() {
active = true;
}
} }

View File

@@ -5,7 +5,6 @@ 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;
@@ -18,19 +17,8 @@ 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;

View File

@@ -11,24 +11,19 @@ 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;
@@ -52,13 +47,12 @@ 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 AtomicBoolean used = new AtomicBoolean(false); private final ReentrantLock lock;
private final ReentrantLock lock = new ReentrantLock();
// The following are locking: lock // The following are locking: lock
private final Map<KeySetId, MutableKeySet> keys = new HashMap<>(); private final Map<Bytes, TagContext> inContexts;
private final Map<Bytes, TagContext> inContexts = new HashMap<>(); private final Map<ContactId, MutableOutgoingKeys> outContexts;
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>(); private final Map<ContactId, MutableTransportKeys> keys;
TransportKeyManagerImpl(DatabaseComponent db, TransportKeyManagerImpl(DatabaseComponent db,
TransportCrypto transportCrypto, Executor dbExecutor, TransportCrypto transportCrypto, Executor dbExecutor,
@@ -71,16 +65,20 @@ 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
Collection<KeySet> loaded = db.getTransportKeys(txn, transportId); Map<ContactId, TransportKeys> loaded =
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
@@ -95,48 +93,41 @@ class TransportKeyManagerImpl implements TransportKeyManager {
scheduleKeyRotation(now); scheduleKeyRotation(now);
} }
private RotationResult rotateKeys(Collection<KeySet> keys, long now) { private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys,
long now) {
RotationResult rotationResult = new RotationResult(); RotationResult rotationResult = new RotationResult();
long rotationPeriod = now / rotationPeriodLength; long rotationPeriod = now / rotationPeriodLength;
for (KeySet ks : keys) { for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
TransportKeys k = ks.getTransportKeys(); ContactId c = e.getKey();
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.add(ks1); rotationResult.rotated.put(c, k1);
rotationResult.current.add(ks1); rotationResult.current.put(c, k1);
} }
return rotationResult; return rotationResult;
} }
// Locking: lock // Locking: lock
private void addKeys(Collection<KeySet> keys) { private void addKeys(Map<ContactId, TransportKeys> m) {
for (KeySet ks : keys) { for (Entry<ContactId, TransportKeys> e : m.entrySet())
addKeys(ks.getKeySetId(), ks.getContactId(), addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
new MutableTransportKeys(ks.getTransportKeys()));
}
} }
// Locking: lock // Locking: lock
private void addKeys(KeySetId keySetId, @Nullable ContactId contactId, private void addKeys(ContactId c, MutableTransportKeys m) {
MutableTransportKeys m) { encodeTags(c, m.getPreviousIncomingKeys());
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m); encodeTags(c, m.getCurrentIncomingKeys());
keys.put(keySetId, ks); encodeTags(c, m.getNextIncomingKeys());
if (contactId != null) { outContexts.put(c, m.getCurrentOutgoingKeys());
encodeTags(keySetId, contactId, m.getPreviousIncomingKeys()); keys.put(c, m);
encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
encodeTags(keySetId, contactId, m.getNextIncomingKeys());
considerReplacingOutgoingKeys(ks);
}
} }
// Locking: lock // Locking: lock
private void encodeTags(KeySetId keySetId, ContactId contactId, private void encodeTags(ContactId c, MutableIncomingKeys inKeys) {
MutableIncomingKeys inKeys) {
for (long streamNumber : inKeys.getWindow().getUnseen()) { for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
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);
@@ -144,17 +135,6 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
// Locking: lock
private void considerReplacingOutgoingKeys(MutableKeySet ks) {
// Use the active outgoing keys with the highest key set ID
if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) {
MutableKeySet old = outContexts.get(ks.getContactId());
if (old == null ||
old.getKeySetId().getInt() < ks.getKeySetId().getInt())
outContexts.put(ks.getContactId(), ks);
}
}
private void scheduleKeyRotation(long now) { 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);
@@ -179,82 +159,20 @@ 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, active); master, rotationPeriod, alice);
// 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);
// Write the keys back to the DB
KeySetId keySetId = db.addTransportKeys(txn, c, k);
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(keySetId, c, new MutableTransportKeys(k)); addKeys(c, new MutableTransportKeys(k));
return keySetId; // Write the keys back to the DB
} finally { db.addTransportKeys(txn, c, k);
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();
} }
@@ -265,29 +183,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
lock.lock(); lock.lock();
try { try {
// Remove mutable state for the contact // Remove mutable state for the contact
Iterator<TagContext> it = inContexts.values().iterator(); Iterator<Entry<Bytes, TagContext>> it =
while (it.hasNext()) if (it.next().contactId.equals(c)) it.remove(); inContexts.entrySet().iterator();
while (it.hasNext())
if (it.next().getValue().contactId.equals(c)) it.remove();
outContexts.remove(c); outContexts.remove(c);
Iterator<MutableKeySet> it1 = keys.values().iterator(); keys.remove(c);
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();
} }
@@ -299,11 +200,8 @@ 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
MutableKeySet ks = outContexts.get(c); MutableOutgoingKeys outKeys = outContexts.get(c);
if (ks == null) return null; if (outKeys == 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,
@@ -311,7 +209,8 @@ 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, transportId, ks.getKeySetId()); db.incrementStreamCounter(txn, c, transportId,
outKeys.getRotationPeriod());
return ctx; return ctx;
} finally { } finally {
lock.unlock(); lock.unlock();
@@ -339,9 +238,8 @@ 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);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId, inContexts.put(new Bytes(addTag), new TagContext(
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()) {
@@ -352,19 +250,9 @@ 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.keySetId, transportId, db.setReorderingWindow(txn, tagCtx.contactId, 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();
@@ -376,11 +264,9 @@ 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
Collection<KeySet> snapshot = new ArrayList<>(keys.size()); Map<ContactId, TransportKeys> snapshot = new HashMap<>();
for (MutableKeySet ks : keys.values()) { for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(), snapshot.put(e.getKey(), e.getValue().snapshot());
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();
@@ -399,14 +285,12 @@ 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(KeySetId keySetId, ContactId contactId, private TagContext(ContactId contactId, MutableIncomingKeys inKeys,
MutableIncomingKeys inKeys, long streamNumber) { long streamNumber) {
this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.inKeys = inKeys; this.inKeys = inKeys;
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
@@ -415,7 +299,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private static class RotationResult { private static class RotationResult {
private final Collection<KeySet> current = new ArrayList<>(); private final Map<ContactId, TransportKeys> current, rotated;
private final Collection<KeySet> rotated = new ArrayList<>();
private RotationResult() {
current = new HashMap<>();
rotated = new HashMap<>();
}
} }
} }

View File

@@ -37,7 +37,6 @@ import java.util.Random;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_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;
@@ -301,34 +300,30 @@ 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[] signed = expectToByteArray(list); byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(signature, label, signed, oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
publicKey);
will(returnValue(true)); will(returnValue(true));
}}); }});
clientHelper.verifySignature(signature, label, list, publicKey); clientHelper.verifySignature(label, rawMessage, publicKey, list);
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[] signed = expectToByteArray(list); byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(signature, label, signed, oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
publicKey);
will(returnValue(false)); will(returnValue(false));
}}); }});
try { try {
clientHelper.verifySignature(signature, label, list, publicKey); clientHelper.verifySignature(label, rawMessage, publicKey, list);
fail(); fail();
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
// expected // expected

View File

@@ -143,9 +143,9 @@ public class EdSignatureTest extends SignatureTest {
} }
@Override @Override
protected boolean verify(byte[] signature, String label, byte[] signed, protected boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] publicKey) throws GeneralSecurityException { byte[] signature) throws GeneralSecurityException {
return crypto.verifySignature(signature, label, signed, publicKey); return crypto.verify(label, signedData, publicKey, signature);
} }
@Test @Test

View File

@@ -17,7 +17,6 @@ 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;
@@ -28,13 +27,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 = getTransportId(); private final TransportId transportId = new TransportId("id");
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, true); master, 123, true);
assertAllDifferent(k); assertAllDifferent(k);
} }
@@ -42,9 +41,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, true); master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false, true); master, 123, false);
// 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());
@@ -74,9 +73,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, true); master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false, true); master, 123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys // 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);
@@ -101,9 +100,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, true); master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false, true); master, 123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in // 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);
@@ -128,20 +127,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, true); master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId, TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
master1, 123, true, true); master1, 123, true);
assertAllDifferent(k, k1); assertAllDifferent(k, k1);
} }
@Test @Test
public void testTransportIdAffectsOutput() { public void testTransportIdAffectsOutput() {
TransportId transportId1 = getTransportId(); TransportId transportId1 = new TransportId("id1");
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, true); master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1, TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
master, 123, true, true); master, 123, true);
assertAllDifferent(k, k1); assertAllDifferent(k, k1);
} }

View File

@@ -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.verifySignature(signature, "test", message, assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
publicKey.getEncoded())); signature));
// 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.verifySignature(signature, "test", message, assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
publicKey.getEncoded())); signature));
} }
@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.verifySignature(signature, "test", message, assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
publicKey.getEncoded())); signature));
// 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.verifySignature(signature1, "test", message, assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
publicKey.getEncoded())); signature1));
assertArrayEquals(signature, signature1); assertArrayEquals(signature, signature1);
} }

View File

@@ -13,7 +13,6 @@ 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 {
@@ -33,7 +32,6 @@ 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
@@ -42,11 +40,6 @@ 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
@@ -55,11 +48,6 @@ 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
@@ -69,11 +57,6 @@ 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));
} }
} }

View File

@@ -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(byte[] signature, String label, protected abstract boolean verify(String label, byte[] signedData,
byte[] signed, byte[] publicKey) throws GeneralSecurityException; byte[] publicKey, byte[] signature) 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(sig, label, inputBytes, publicKey)); assertTrue(verify(label, inputBytes, publicKey, sig));
} }
@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(sig, label, inputBytes, publicKey)); assertFalse(verify(label, inputBytes, publicKey, sig));
} }
@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(sig, label, inputBytes2, publicKey)); assertFalse(verify(label, inputBytes2, publicKey, sig));
} }
@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(sig, label2, inputBytes, publicKey)); assertFalse(verify(label2, inputBytes, publicKey, sig));
} }
} }

View File

@@ -44,36 +44,34 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.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.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString;
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;
@@ -102,28 +100,27 @@ 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 = getClientId(); clientId = new ClientId(getRandomString(123));
group = getGroup(clientId); groupId = new GroupId(TestUtils.getRandomId());
groupId = group.getId(); byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor);
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); localAuthor = getLocalAuthor();
messageId = new MessageId(getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
messageId1 = new MessageId(getRandomId()); messageId1 = new MessageId(TestUtils.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 = getTransportId(); transportId = new TransportId("id");
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,
@@ -285,11 +282,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(17).of(database).startTransaction(); exactly(18).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(17).of(database).containsContact(txn, contactId); exactly(18).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(17).of(database).abortTransaction(txn); exactly(18).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
@@ -304,16 +301,6 @@ 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);
@@ -384,6 +371,16 @@ 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);
@@ -457,6 +454,17 @@ 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);
@@ -769,13 +777,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(6).of(database).startTransaction(); exactly(4).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); exactly(2).of(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
exactly(6).of(database).containsTransport(txn, transportId); exactly(4).of(database).containsTransport(txn, transportId);
will(returnValue(false)); will(returnValue(false));
exactly(6).of(database).abortTransaction(txn); exactly(4).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
@@ -790,16 +798,6 @@ 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);
@@ -812,7 +810,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.incrementStreamCounter(transaction, transportId, keySetId); db.incrementStreamCounter(transaction, contactId, transportId, 0);
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
@@ -832,17 +830,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.removeTransportKeys(transaction, transportId, keySetId); db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
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) {
@@ -919,7 +907,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testGenerateOffer() throws Exception { public void testGenerateOffer() throws Exception {
MessageId messageId1 = new MessageId(getRandomId()); MessageId messageId1 = new MessageId(TestUtils.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();
@@ -950,7 +938,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testGenerateRequest() throws Exception { public void testGenerateRequest() throws Exception {
MessageId messageId1 = new MessageId(getRandomId()); MessageId messageId1 = new MessageId(TestUtils.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();
@@ -1138,9 +1126,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testReceiveOffer() throws Exception { public void testReceiveOffer() throws Exception {
MessageId messageId1 = new MessageId(getRandomId()); MessageId messageId1 = new MessageId(TestUtils.getRandomId());
MessageId messageId2 = new MessageId(getRandomId()); MessageId messageId2 = new MessageId(TestUtils.getRandomId());
MessageId messageId3 = new MessageId(getRandomId()); MessageId messageId3 = new MessageId(TestUtils.getRandomId());
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1315,13 +1303,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); TransportKeys transportKeys = createTransportKeys();
Collection<KeySet> keys = Map<ContactId, TransportKeys> keys =
singletonList(new KeySet(keySetId, contactId, transportKeys)); singletonMap(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);
@@ -1347,22 +1337,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
private TransportKeys createTransportKeys() { private TransportKeys createTransportKeys() {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = TestUtils.getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = TestUtils.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 = getSecretKey(); SecretKey inCurrTagKey = TestUtils.getSecretKey();
SecretKey inCurrHeaderKey = getSecretKey(); SecretKey inCurrHeaderKey = TestUtils.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 = getSecretKey(); SecretKey inNextTagKey = TestUtils.getSecretKey();
SecretKey inNextHeaderKey = getSecretKey(); SecretKey inNextHeaderKey = TestUtils.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 = getSecretKey(); SecretKey outCurrTagKey = TestUtils.getSecretKey();
SecretKey outCurrHeaderKey = getSecretKey(); SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
2, 456, true); 2, 456);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }
@@ -1508,7 +1498,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(getRandomId()); MessageId messageId2 = new MessageId(TestUtils.getRandomId());
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(null); oneOf(database).open(null);
@@ -1528,12 +1518,10 @@ 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).getMessageState(txn, messageId); oneOf(database).addMessageDependency(txn, groupId, messageId,
will(returnValue(DELIVERED)); messageId1);
oneOf(database).addMessageDependency(txn, message, messageId1, oneOf(database).addMessageDependency(txn, groupId, messageId,
DELIVERED); messageId2);
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));

View File

@@ -572,9 +572,8 @@ 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) {
MessageId dependency = db.addMessageDependency(txn, g.getId(), m.getId(),
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());
} }
@@ -599,9 +598,8 @@ 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) {
MessageId dependency = db.addMessageDependency(txn, g.getId(), m.getId(),
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());
} }

View File

@@ -19,8 +19,6 @@ 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;
@@ -36,6 +34,7 @@ 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;
@@ -43,28 +42,23 @@ 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.test.TestUtils.getTransportId; 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.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -92,12 +86,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 {
clientId = getClientId(); groupId = new GroupId(getRandomId());
group = getGroup(clientId); clientId = new ClientId(getRandomString(123));
groupId = group.getId(); byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor);
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); localAuthor = getLocalAuthor();
messageId = new MessageId(getRandomId()); messageId = new MessageId(getRandomId());
@@ -105,10 +99,8 @@ 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 = getTransportId(); transportId = new TransportId("id");
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,
@@ -198,9 +190,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
// Changing the status to seen = true should make the message unsendable // Changing the status to seen = true should make the message unsendable
db.raiseSeenFlag(txn, contactId, messageId); db.raiseSeenFlag(txn, contactId, messageId);
@@ -236,9 +228,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(singletonList(messageId), ids); assertEquals(Collections.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);
@@ -287,9 +279,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(singletonList(messageId), ids); assertEquals(Collections.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);
@@ -332,9 +324,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -360,7 +352,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -392,7 +384,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(emptyList(), db.getMessagesToAck(txn, assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
contactId, 1234)); contactId, 1234));
// Raise the ack flag again // Raise the ack flag again
@@ -423,7 +415,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Retrieve the message from the database and mark it as sent // 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(singletonList(messageId), ids); assertEquals(Collections.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
@@ -634,31 +626,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(emptyMap(), assertEquals(Collections.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(singletonMap(contactId, false), assertEquals(Collections.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(singletonMap(contactId, true), assertEquals(Collections.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(singletonMap(contactId, false), assertEquals(Collections.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(emptyMap(), assertEquals(Collections.emptyMap(),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -668,125 +660,48 @@ 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(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(Collections.emptyMap(),
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);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); db.addTransportKeys(txn, contactId, keys);
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
// Retrieve the transport keys // Retrieve the transport keys
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId); Map<ContactId, TransportKeys> newKeys =
assertEquals(2, allKeys.size()); db.getTransportKeys(txn, transportId);
for (KeySet ks : allKeys) { assertEquals(1, newKeys.size());
assertEquals(contactId, ks.getContactId()); Entry<ContactId, TransportKeys> e =
if (ks.getKeySetId().equals(keySetId)) { newKeys.entrySet().iterator().next();
assertKeysEquals(keys, ks.getTransportKeys()); assertEquals(contactId, e.getKey());
} else { TransportKeys k = e.getValue();
assertEquals(keySetId1, ks.getKeySetId()); assertEquals(transportId, k.getTransportId());
assertKeysEquals(keys1, ks.getTransportKeys()); assertKeysEquals(keys.getPreviousIncomingKeys(),
} k.getPreviousIncomingKeys());
} assertKeysEquals(keys.getCurrentIncomingKeys(),
k.getCurrentIncomingKeys());
assertKeysEquals(keys.getNextIncomingKeys(),
k.getNextIncomingKeys());
assertKeysEquals(keys.getCurrentOutgoingKeys(),
k.getCurrentOutgoingKeys());
// Removing the contact should remove the transport keys // Removing the contact should remove the transport keys
db.removeContact(txn, contactId); db.removeContact(txn, contactId);
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(Collections.emptyMap(),
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());
@@ -804,7 +719,6 @@ 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
@@ -821,18 +735,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, db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
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, transportId, keySetId); db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
db.incrementStreamCounter(txn, transportId, keySetId); db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId); Map<ContactId, TransportKeys> newKeys =
db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
KeySet ks = newKeys.iterator().next(); Entry<ContactId, TransportKeys> e =
assertEquals(keySetId, ks.getKeySetId()); newKeys.entrySet().iterator().next();
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, e.getKey());
TransportKeys k = ks.getTransportKeys(); TransportKeys k = e.getValue();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
assertEquals(rotationPeriod, outCurr.getRotationPeriod()); assertEquals(rotationPeriod, outCurr.getRotationPeriod());
@@ -857,19 +771,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, db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
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, keySetId, transportId, rotationPeriod, db.setReorderingWindow(txn, contactId, transportId, rotationPeriod,
base + 1, bitmap); base + 1, bitmap);
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId); Map<ContactId, TransportKeys> newKeys =
db.getTransportKeys(txn, transportId);
assertEquals(1, newKeys.size()); assertEquals(1, newKeys.size());
KeySet ks = newKeys.iterator().next(); Entry<ContactId, TransportKeys> e =
assertEquals(keySetId, ks.getKeySetId()); newKeys.entrySet().iterator().next();
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, e.getKey());
TransportKeys k = ks.getTransportKeys(); TransportKeys k = e.getValue();
assertEquals(transportId, k.getTransportId()); assertEquals(transportId, k.getTransportId());
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
assertEquals(rotationPeriod, inCurr.getRotationPeriod()); assertEquals(rotationPeriod, inCurr.getRotationPeriod());
@@ -916,18 +830,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(emptyList(), contacts); assertEquals(Collections.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(singletonList(contactId), contacts); assertEquals(Collections.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(emptyList(), contacts); assertEquals(Collections.emptyList(), contacts);
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -1313,8 +1227,6 @@ 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();
@@ -1322,21 +1234,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, PENDING, true, contactId); db.addMessage(txn, message1, DELIVERED, true, contactId);
db.addMessage(txn, message2, INVALID, true, contactId); db.addMessage(txn, message2, INVALID, true, contactId);
// Add dependencies // Add dependencies
db.addMessageDependency(txn, message, messageId1, PENDING); db.addMessageDependency(txn, groupId, messageId, messageId1);
db.addMessageDependency(txn, message, messageId2, PENDING); db.addMessageDependency(txn, groupId, messageId, messageId2);
db.addMessageDependency(txn, message1, messageId3, PENDING); db.addMessageDependency(txn, groupId, messageId1, messageId3);
db.addMessageDependency(txn, message2, messageId4, INVALID); db.addMessageDependency(txn, groupId, messageId2, messageId4);
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(PENDING, dependencies.get(messageId1)); assertEquals(DELIVERED, dependencies.get(messageId1));
assertEquals(INVALID, dependencies.get(messageId2)); assertEquals(INVALID, dependencies.get(messageId2));
// Retrieve dependencies for message 1 // Retrieve dependencies for message 1
@@ -1369,24 +1281,10 @@ 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(PENDING, dependents.get(messageId1)); assertEquals(DELIVERED, 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);
@@ -1407,8 +1305,9 @@ 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
Group group1 = getGroup(clientId); GroupId groupId1 = new GroupId(getRandomId());
GroupId groupId1 = group1.getId(); Group group1 = new Group(groupId1, clientId,
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
@@ -1425,16 +1324,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, message, messageId1, PENDING); db.addMessageDependency(txn, groupId, messageId, messageId1);
db.addMessageDependency(txn, message, messageId2, PENDING); db.addMessageDependency(txn, groupId, messageId, messageId2);
db.addMessageDependency(txn, message, messageId3, PENDING); db.addMessageDependency(txn, groupId, messageId, messageId3);
// 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 UNKNOWN // The cross-group dependency should have state INVALID
assertEquals(UNKNOWN, dependencies.get(messageId1)); assertEquals(INVALID, 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));
@@ -1446,8 +1345,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 be excluded // The cross-group dependent should have its real state
assertFalse(dependents.containsKey(messageId)); assertEquals(PENDING, dependents.get(messageId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1512,9 +1411,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, m1, mId2, DELIVERED); db.addMessageDependency(txn, groupId, mId1, mId2);
db.addMessageDependency(txn, m3, mId1, DELIVERED); db.addMessageDependency(txn, groupId, mId3, mId1);
db.addMessageDependency(txn, m4, mId3, DELIVERED); db.addMessageDependency(txn, groupId, mId4, mId3);
// Retrieve messages to be shared // Retrieve messages to be shared
Collection<MessageId> result = db.getMessagesToShare(txn); Collection<MessageId> result = db.getMessagesToShare(txn);
@@ -1645,9 +1544,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(singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(singletonList(messageId), ids); assertEquals(Collections.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));
@@ -1820,7 +1719,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, true); 2, 456);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }

View File

@@ -19,7 +19,6 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RE
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT; import static org.briarproject.bramble.api.keyagreement.RecordTypes.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;
@@ -32,7 +31,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 = getTransportId(); private final TransportId transportId = new TransportId("test");
private final KeyAgreementConnection keyAgreementConnection = private final KeyAgreementConnection keyAgreementConnection =
new KeyAgreementConnection(duplexTransportConnection, transportId); new KeyAgreementConnection(duplexTransportConnection, transportId);

View File

@@ -17,7 +17,6 @@ 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;
@@ -30,8 +29,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 = getTransportId(); transportId = new TransportId("id");
transportId1 = getTransportId(); transportId1 = new TransportId("id1");
} }
@Test @Test

View File

@@ -24,8 +24,6 @@ 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
@@ -48,21 +46,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 = getTransportId(); TransportId simplexId = new TransportId("simplex");
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 = getTransportId(); TransportId simplexFailId = new TransportId("simplex1");
// 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 = getTransportId(); TransportId duplexId = new TransportId("duplex");
DuplexPluginFactory duplexFailFactory = DuplexPluginFactory duplexFailFactory =
context.mock(DuplexPluginFactory.class, "duplexFailFactory"); context.mock(DuplexPluginFactory.class, "duplexFailFactory");
TransportId duplexFailId = getTransportId(); TransportId duplexFailId = new TransportId("duplex1");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(simplexPlugin).getId(); allowing(simplexPlugin).getId();

View File

@@ -32,7 +32,6 @@ 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 {
@@ -49,7 +48,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 = getTransportId(); private final TransportId transportId = new TransportId("id");
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();
@@ -65,7 +64,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 = getTransportId(); TransportId simplexId1 = new TransportId("simplex1");
List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin, List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
simplexPlugin1); simplexPlugin1);
TransportConnectionWriter simplexWriter = TransportConnectionWriter simplexWriter =
@@ -73,7 +72,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 = getTransportId(); TransportId duplexId = new TransportId("duplex");
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,
@@ -350,6 +349,7 @@ 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();

View File

@@ -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(CLIENT_ID); private final Group localGroup = getGroup();
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,8 +90,7 @@ 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(CLIENT_ID); Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
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());
@@ -144,7 +143,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(CLIENT_ID); Group contactGroup = getGroup();
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
@@ -172,7 +171,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(CLIENT_ID); Group contactGroup = getGroup();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
@@ -307,7 +306,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(CLIENT_ID); Group contactGroup = getGroup();
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Map<TransportId, TransportProperties> properties = Map<TransportId, TransportProperties> properties =
new LinkedHashMap<>(); new LinkedHashMap<>();
@@ -400,9 +399,6 @@ 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);
}}); }});
@@ -421,8 +417,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(CLIENT_ID); Group contactGroup2 = getGroup();
Group contactGroup3 = getGroup(CLIENT_ID); Group contactGroup3 = getGroup();
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
@@ -470,9 +466,6 @@ 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);
}}); }});
@@ -508,9 +501,6 @@ 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);
@@ -524,7 +514,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(CLIENT_ID); Group contactGroup = getGroup();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -559,7 +549,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(CLIENT_ID); Group contactGroup = getGroup();
BdfDictionary oldMetadata = BdfDictionary.of( BdfDictionary oldMetadata = BdfDictionary.of(
new BdfEntry("transportId", "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry("version", 1), new BdfEntry("version", 1),
@@ -571,12 +561,9 @@ 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);
TransportProperties oldProperties = new TransportProperties(); BdfList oldUpdate = BdfList.of("foo", 1, BdfDictionary.of(
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);
@@ -587,9 +574,6 @@ 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);
@@ -616,6 +600,12 @@ 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(),
@@ -653,14 +643,8 @@ 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));
}}); }});
} }

View File

@@ -3,78 +3,80 @@ 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.properties.TransportProperties; 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.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.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.jmock.Expectations; import org.briarproject.bramble.test.TestUtils;
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.test.TestUtils.getClientId; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
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 BrambleMockTestCase { public class TransportPropertyValidatorTest extends BrambleTestCase {
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 = getTransportId(); transportId = new TransportId("test");
bdfDictionary = BdfDictionary.of(new BdfEntry("foo", "bar")); bdfDictionary = new BdfDictionary();
transportProperties = new TransportProperties();
transportProperties.put("foo", "bar");
group = getGroup(getClientId()); GroupId groupId = new GroupId(TestUtils.getRandomId());
message = getMessage(group.getId()); ClientId clientId = new ClientId(StringUtils.getRandomString(5));
byte[] descriptor = TestUtils.getRandomBytes(12);
group = new Group(groupId, clientId, descriptor);
MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
byte[] body = TestUtils.getRandomBytes(123);
message = new Message(messageId, groupId, timestamp, body);
Mockery context = new Mockery();
ClientHelper clientHelper = context.mock(ClientHelper.class);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); 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);
context.checking(new Expectations() {{ BdfDictionary result = tpv.validateMessage(message, group, body)
oneOf(clientHelper).parseAndValidateTransportProperties( .getDictionary();
bdfDictionary);
will(returnValue(transportProperties));
}});
BdfDictionary result = assertEquals("test", result.getString("transportId"));
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);
@@ -82,15 +84,27 @@ public class TransportPropertyValidatorTest extends BrambleMockTestCase {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testValidateLongTransportId() throws IOException { public void testValidateLongTransportId() throws IOException {
String wrongTransportIdString = String wrongTransportIdString =
getRandomString(MAX_TRANSPORT_ID_LENGTH + 1); StringUtils.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);
}
} }

View File

@@ -36,8 +36,7 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.bramble.api.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.test.TestUtils.getClientId; 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;
@@ -74,13 +73,13 @@ public class SyncIntegrationTest extends BrambleTestCase {
component.inject(this); component.inject(this);
contactId = new ContactId(234); contactId = new ContactId(234);
transportId = getTransportId(); transportId = new TransportId("id");
// 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 = getClientId(); ClientId clientId = new ClientId(getRandomString(123));
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,

View File

@@ -21,7 +21,9 @@ 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;
@@ -36,9 +38,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.ValidationManager.State.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 {
@@ -52,12 +51,14 @@ 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 = getClientId(); private final ClientId clientId =
private final MessageId messageId = new MessageId(getRandomId()); new ClientId(StringUtils.getRandomString(5));
private final MessageId messageId1 = new MessageId(getRandomId()); private final MessageId messageId = new MessageId(TestUtils.getRandomId());
private final MessageId messageId2 = new MessageId(getRandomId()); private final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
private final Group group = getGroup(clientId); private final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
private final GroupId groupId = group.getId(); private final GroupId groupId = new GroupId(TestUtils.getRandomId());
private final byte[] descriptor = new byte[32];
private final Group group = new Group(groupId, clientId, descriptor);
private final long timestamp = System.currentTimeMillis(); private final 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,
@@ -715,8 +716,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testRecursiveInvalidation() throws Exception { public void testRecursiveInvalidation() throws Exception {
MessageId messageId3 = new MessageId(getRandomId()); MessageId messageId3 = new MessageId(TestUtils.getRandomId());
MessageId messageId4 = new MessageId(getRandomId()); MessageId messageId4 = new MessageId(TestUtils.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);
@@ -818,8 +819,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testPendingDependentsGetDelivered() throws Exception { public void testPendingDependentsGetDelivered() throws Exception {
MessageId messageId3 = new MessageId(getRandomId()); MessageId messageId3 = new MessageId(TestUtils.getRandomId());
MessageId messageId4 = new MessageId(getRandomId()); MessageId messageId4 = new MessageId(TestUtils.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,

View File

@@ -16,12 +16,10 @@ 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 = getTransportId(); public static final TransportId TRANSPORT_ID = new TransportId("id");
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
@NotNullByDefault @NotNullByDefault

View File

@@ -1,20 +1,18 @@
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.Author; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.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.getAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.test.TestUtils.getMessage;
public abstract class ValidatorTestCase extends BrambleMockTestCase { public abstract class ValidatorTestCase extends BrambleMockTestCase {
@@ -23,27 +21,17 @@ 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 Group group = getGroup(getClientId()); protected final MessageId messageId = new MessageId(getRandomId());
protected final GroupId groupId = group.getId(); protected final GroupId groupId = new GroupId(getRandomId());
protected final byte[] descriptor = group.getDescriptor(); protected final long timestamp = 1234567890 * 1000L;
protected final Message message = getMessage(groupId); protected final byte[] raw = getRandomBytes(123);
protected final MessageId messageId = message.getId(); protected final Message message =
protected final long timestamp = message.getTimestamp(); new Message(messageId, groupId, timestamp, raw);
protected final byte[] raw = message.getRaw(); protected final ClientId clientId = new ClientId(getRandomString(123));
protected final Author author = getAuthor(); protected final byte[] descriptor = getRandomBytes(123);
protected final BdfList authorList = BdfList.of( protected final Group group = new Group(groupId, clientId, descriptor);
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));
}});
}
}

View File

@@ -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.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleTestCase;
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 BrambleMockTestCase { public class KeyManagerImplTest extends BrambleTestCase {
private final Mockery context = new Mockery();
private final KeyManagerImpl keyManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final 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(123); private final ContactId contactId = new ContactId(42);
private final ContactId inactiveContactId = new ContactId(234); private final ContactId inactiveContactId = new ContactId(43);
private final KeySetId keySetId = new KeySetId(345); private final TransportId transportId = new TransportId("tId");
private final TransportId transportId = getTransportId(); private final TransportId unknownTransportId = new TransportId("id");
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);
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor, public KeyManagerImplTest() {
pluginConfig, transportKeyManagerFactory); keyManager = new KeyManagerImpl(db, executor, pluginConfig,
transportKeyManagerFactory);
}
@Before @Before
public void testStartService() throws Exception { public void testStartService() throws Exception {
@@ -70,8 +70,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
true, false)); true, false));
SimplexPluginFactory pluginFactory = SimplexPluginFactory pluginFactory =
context.mock(SimplexPluginFactory.class); context.mock(SimplexPluginFactory.class);
Collection<SimplexPluginFactory> factories = Collection<SimplexPluginFactory> factories = Collections
singletonList(pluginFactory); .singletonList(pluginFactory);
int maxLatency = 1337; int maxLatency = 1337;
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -110,22 +110,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
}}); }});
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
@@ -153,6 +138,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
assertEquals(streamContext, assertEquals(streamContext,
keyManager.getStreamContext(contactId, transportId)); keyManager.getStreamContext(contactId, transportId));
context.assertIsSatisfied();
} }
@Test @Test
@@ -175,6 +161,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
assertEquals(streamContext, assertEquals(streamContext,
keyManager.getStreamContext(transportId, tag)); keyManager.getStreamContext(transportId, tag));
context.assertIsSatisfied();
} }
@Test @Test
@@ -188,6 +175,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
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
@@ -207,5 +196,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
keyManager.eventOccurred(event); keyManager.eventOccurred(event);
assertEquals(streamContext, assertEquals(streamContext,
keyManager.getStreamContext(inactiveContactId, transportId)); keyManager.getStreamContext(inactiveContactId, transportId));
context.assertIsSatisfied();
} }
} }

View File

@@ -8,8 +8,6 @@ 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;
@@ -24,25 +22,23 @@ 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 {
@@ -54,14 +50,11 @@ 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 = getTransportId(); private final TransportId transportId = new TransportId("id");
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();
@@ -69,16 +62,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testKeysAreRotatedAtStartup() throws Exception { public void testKeysAreRotatedAtStartup() throws Exception {
TransportKeys shouldRotate = createTransportKeys(900, 0, true); Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true); TransportKeys shouldRotate = createTransportKeys(900, 0);
TransportKeys shouldRotate1 = createTransportKeys(999, 0, false); TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
Collection<KeySet> loaded = asList( loaded.put(contactId, shouldRotate);
new KeySet(keySetId, contactId, shouldRotate), loaded.put(contactId1, shouldNotRotate);
new KeySet(keySetId1, contactId1, shouldNotRotate), TransportKeys rotated = createTransportKeys(1000, 0);
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() {{
@@ -93,8 +82,6 @@ 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(
@@ -103,10 +90,8 @@ 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, asList( oneOf(db).updateTransportKeys(txn,
new KeySet(keySetId, contactId, rotated), Collections.singletonMap(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));
@@ -116,19 +101,18 @@ 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, true); TransportKeys transportKeys = createTransportKeys(999, 0);
TransportKeys rotated = createTransportKeys(1000, 0, true); TransportKeys rotated = createTransportKeys(1000, 0);
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, true); 999, alice);
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();
@@ -145,7 +129,6 @@ 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(
@@ -155,39 +138,6 @@ 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
@@ -199,7 +149,6 @@ 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
@@ -208,10 +157,29 @@ 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, true); MAX_32_BIT_UNSIGNED + 1);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, transportKeys, txn); context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
@@ -220,7 +188,6 @@ 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));
} }
@@ -229,14 +196,30 @@ 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, true); MAX_32_BIT_UNSIGNED);
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, transportId, keySetId); oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
}}); }});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
@@ -247,7 +230,6 @@ 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);
@@ -257,7 +239,6 @@ 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));
} }
@@ -265,10 +246,29 @@ 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, true); TransportKeys transportKeys = createTransportKeys(1000, 0);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, transportKeys, txn); context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
@@ -277,8 +277,6 @@ 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]));
} }
@@ -286,15 +284,14 @@ 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, true); TransportKeys transportKeys = createTransportKeys(1000, 0);
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, true); 1000, alice);
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();
@@ -311,14 +308,13 @@ 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, keySetId, transportId, 999, oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
1, new byte[REORDERING_WINDOW_SIZE / 8]); 1, new byte[REORDERING_WINDOW_SIZE / 8]);
}}); }});
@@ -329,7 +325,6 @@ 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);
@@ -349,10 +344,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testKeysAreRotatedToCurrentPeriod() throws Exception { public void testKeysAreRotatedToCurrentPeriod() throws Exception {
TransportKeys transportKeys = createTransportKeys(1000, 0, true); TransportKeys transportKeys = createTransportKeys(1000, 0);
Collection<KeySet> loaded = Map<ContactId, TransportKeys> loaded =
singletonList(new KeySet(keySetId, contactId, transportKeys)); Collections.singletonMap(contactId, transportKeys);
TransportKeys rotated = createTransportKeys(1001, 0, true); TransportKeys rotated = createTransportKeys(1001, 0);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Transaction txn1 = new Transaction(null, false); Transaction txn1 = new Transaction(null, false);
@@ -398,7 +393,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
} }
// Save the keys that were rotated // Save the keys that were rotated
oneOf(db).updateTransportKeys(txn1, oneOf(db).updateTransportKeys(txn1,
singletonList(new KeySet(keySetId, contactId, rotated))); Collections.singletonMap(contactId, rotated));
// Schedule key rotation at the start of the next rotation period // 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));
@@ -411,197 +406,10 @@ 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, boolean active) { long streamCounter) {
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,
@@ -609,7 +417,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, active); rotationPeriod, streamCounter);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }

View File

@@ -352,16 +352,6 @@
/> />
</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"
@@ -384,12 +374,7 @@
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.logout.ExitActivity" android:name="org.briarproject.briar.android.panic.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>

View File

@@ -62,7 +62,6 @@ 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;
@@ -91,6 +90,12 @@ 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 =
@@ -169,8 +174,6 @@ 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));

View File

@@ -4,11 +4,8 @@ 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;
@@ -20,26 +17,19 @@ 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;
@@ -71,9 +61,6 @@ 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
@@ -153,19 +140,6 @@ 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) {
@@ -210,7 +184,6 @@ 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();
@@ -221,48 +194,7 @@ 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");
shutdownFromBackground(); // FIXME: Work out what to do about it
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());
});
} }
/** /**

View File

@@ -70,7 +70,6 @@ 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;
@@ -148,8 +147,6 @@ 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);

View File

@@ -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.logout.ExitActivity; import org.briarproject.briar.android.panic.ExitActivity;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@@ -781,18 +781,25 @@ public class ConversationActivity extends BriarActivity
return; return;
} }
PromptStateChangeListener listener = (prompt, state) -> { PromptStateChangeListener listener = new PromptStateChangeListener() {
if (state == STATE_DISMISSED || state == STATE_FINISHED) { @Override
introductionOnboardingSeen(); public void onPromptStateChanged(
} MaterialTapTargetPrompt prompt, int state) {
}; if (state == STATE_DISMISSED ||
new MaterialTapTargetPrompt.Builder(ConversationActivity.this, state == STATE_FINISHED) {
R.style.OnboardingDialogTheme).setTarget(target) introductionOnboardingSeen();
.setPrimaryText(R.string.introduction_onboarding_title) }
.setSecondaryText(R.string.introduction_onboarding_text) }
.setIcon(R.drawable.ic_more_vert_accent)
.setPromptStateChangeListener(listener) };
.show(); new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
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();
}); });
} }
@@ -858,12 +865,10 @@ public class ConversationActivity extends BriarActivity
"Unknown Request Type"); "Unknown Request Type");
} }
loadMessages(); loadMessages();
} catch (ProtocolStateException e) { } catch (DbException | FormatException e) {
// Action is no longer valid - reloading should solve the issue introductionResponseError();
if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e); if (LOG.isLoggable(WARNING))
} catch (DbException e) { LOG.log(WARNING, e.toString(), e);
// TODO show an error message
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
}); });
} }
@@ -893,9 +898,12 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor @DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId, private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException { boolean accept, long time) throws DbException, FormatException {
introductionManager.respondToIntroduction(contactId, sessionId, time, if (accept) {
accept); introductionManager.acceptIntroduction(contactId, sessionId, time);
} else {
introductionManager.declineIntroduction(contactId, sessionId, time);
}
} }
@DatabaseExecutor @DatabaseExecutor
@@ -913,7 +921,18 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor @DatabaseExecutor
private void respondToGroupRequest(SessionId id, boolean accept) private void respondToGroupRequest(SessionId id, boolean accept)
throws DbException { throws DbException {
groupInvitationManager.respondToInvitation(contactId, id, accept); try {
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() {

View File

@@ -124,11 +124,6 @@ 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,

View File

@@ -52,15 +52,19 @@ 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.getColor(ctx, R.color.briar_text_secondary)); ContextCompat
.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.getColor(ctx, R.color.briar_text_tertiary)); ContextCompat
.getColor(ctx, R.color.briar_text_tertiary));
} }
// Date // Date

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.logout; package org.briarproject.briar.android.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -7,17 +7,15 @@ 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 {
public static final String TAG = SignOutFragment.class.getName(); private static final String TAG = SignOutFragment.class.getName();
@Override @Override
public View onCreateView(@Nonnull LayoutInflater inflater, public View onCreateView(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);
@@ -32,4 +30,5 @@ public class SignOutFragment extends BaseFragment {
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
// no need to inject // no need to inject
} }
} }

View File

@@ -2,7 +2,6 @@ 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;
@@ -12,6 +11,7 @@ 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,10 +33,9 @@ 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_REQUEST_MESSAGE_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
public class IntroductionMessageFragment extends BaseFragment public class IntroductionMessageFragment extends BaseFragment
implements TextInputListener { implements TextInputListener {
@@ -126,15 +125,14 @@ 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));
boolean possible = introductionManager.canIntroduce(c1, c2); setUpViews(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, boolean possible) { private void setUpViews(Contact c1, Contact c2) {
introductionActivity.runOnUiThreadUnlessDestroyed(() -> { introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
contact1 = c1; contact1 = c1;
contact2 = c2; contact2 = c2;
@@ -149,22 +147,13 @@ 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());
// hide progress bar // set button action
ui.message.setListener(IntroductionMessageFragment.this);
// hide progress bar and show views
ui.progressBar.setVisibility(GONE); ui.progressBar.setVisibility(GONE);
ui.message.setSendButtonEnabled(true);
if (possible) { ui.message.showSoftKeyboard();
// 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);
}
}); });
} }
@@ -186,8 +175,7 @@ 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();
if (msg.equals("")) msg = null; msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH);
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
@@ -196,14 +184,13 @@ public class IntroductionMessageFragment extends BaseFragment
introductionActivity.supportFinishAfterTransition(); introductionActivity.supportFinishAfterTransition();
} }
private void makeIntroduction(Contact c1, Contact c2, private void makeIntroduction(Contact c1, Contact c2, String msg) {
@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 e) { } catch (DbException | FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
introductionError(); introductionError();
} }
@@ -221,7 +208,6 @@ 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) {
@@ -230,7 +216,6 @@ 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);
} }
} }

View File

@@ -14,8 +14,6 @@ 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;
@@ -54,8 +52,6 @@ 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;
@@ -88,7 +84,6 @@ 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;
@@ -133,34 +128,6 @@ 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
@@ -317,7 +284,6 @@ 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)) {

View File

@@ -1,20 +0,0 @@
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) {
}
}

View File

@@ -7,7 +7,6 @@ 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;
@@ -23,7 +22,6 @@ 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;
@@ -37,7 +35,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.logout.SignOutFragment; import org.briarproject.briar.android.fragment.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;
@@ -53,7 +51,6 @@ 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;
@@ -76,8 +73,6 @@ 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;
@@ -133,9 +128,7 @@ public class NavDrawerActivity extends BriarActivity implements
initializeTransports(getLayoutInflater()); initializeTransports(getLayoutInflater());
transportsView.setAdapter(transportsAdapter); transportsView.setAdapter(transportsAdapter);
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { if (state == null) {
showSignOutFragment();
} else if (state == null) {
startFragment(ContactListFragment.newInstance(), startFragment(ContactListFragment.newInstance(),
R.id.nav_btn_contacts); R.id.nav_btn_contacts);
} }
@@ -219,23 +212,19 @@ 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 { } else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
FragmentManager fm = getSupportFragmentManager(); getSupportFragmentManager()
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) { .findFragmentByTag(ContactListFragment.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();
}
} }
} }
@@ -251,15 +240,10 @@ public class NavDrawerActivity extends BriarActivity implements
drawerToggle.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig);
} }
private void showSignOutFragment() {
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
startFragment(new SignOutFragment());
}
private void signOut() { private void signOut() {
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED); drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
startFragment(new SignOutFragment());
signOut(false); signOut(false);
finish();
} }
private void startFragment(BaseFragment fragment, int itemId) { private void startFragment(BaseFragment fragment, int itemId) {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.logout; package org.briarproject.briar.android.panic;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;

View File

@@ -74,7 +74,9 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
if (group.isEmpty()) { if (group.isEmpty()) {
postCount.setVisibility(GONE); postCount.setVisibility(GONE);
date.setVisibility(GONE); date.setVisibility(GONE);
status.setText(ctx.getString(R.string.groups_group_is_empty)); avatar.setProblem(true);
status
.setText(ctx.getString(R.string.groups_group_is_empty));
status.setVisibility(VISIBLE); status.setVisibility(VISIBLE);
} else { } else {
// Message Count // Message Count
@@ -89,6 +91,7 @@ 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);

View File

@@ -173,12 +173,7 @@ public class BriarReportPrimer implements ReportPrimer {
WifiInfo wifiInfo = wm.getConnectionInfo(); WifiInfo wifiInfo = wm.getConnectionInfo();
if (wifiInfo != null) { if (wifiInfo != null) {
int ip = wifiInfo.getIpAddress(); // Nice API, Google int ip = wifiInfo.getIpAddress(); // Nice API, Google
int ip1 = ip & 0xFF; customData.put("Wi-Fi address", StringUtils.ipToString(ip));
int ip2 = (ip >> 8) & 0xFF;
int ip3 = (ip >> 16) & 0xFF;
int ip4 = (ip >> 24) & 0xFF;
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
customData.put("Wi-Fi address", address);
} }
} }

View File

@@ -1,13 +1,12 @@
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;
@@ -30,6 +29,7 @@ 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,10 +44,6 @@ 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;
@@ -57,10 +53,6 @@ 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;
@@ -104,6 +96,8 @@ 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) {
@@ -138,10 +132,33 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this);
if (SDK_INT >= 21) { notifyPrivateMessages.setOnPreferenceChangeListener(this);
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 -> {
@@ -150,7 +167,14 @@ 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);
} }
@@ -196,54 +220,36 @@ 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));
if (SDK_INT < 26) { notifyPrivateMessages.setChecked(settings.getBoolean(
notifyPrivateMessages.setChecked(settings.getBoolean( PREF_NOTIFY_PRIVATE, true));
PREF_NOTIFY_PRIVATE, true));
notifyGroupMessages.setChecked(settings.getBoolean( notifyGroupMessages.setChecked(settings.getBoolean(
PREF_NOTIFY_GROUP, true)); PREF_NOTIFY_GROUP, true));
notifyForumPosts.setChecked(settings.getBoolean(
PREF_NOTIFY_FORUM, true)); notifyForumPosts.setChecked(settings.getBoolean(
notifyBlogPosts.setChecked(settings.getBoolean( PREF_NOTIFY_FORUM, true));
PREF_NOTIFY_BLOG, true));
notifyVibration.setChecked(settings.getBoolean( notifyBlogPosts.setChecked(settings.getBoolean(
PREF_NOTIFY_VIBRATION, true)); PREF_NOTIFY_BLOG, true));
notifyPrivateMessages.setOnPreferenceChangeListener(this);
notifyGroupMessages.setOnPreferenceChangeListener(this); notifyVibration.setChecked(settings.getBoolean(
notifyForumPosts.setOnPreferenceChangeListener(this); PREF_NOTIFY_VIBRATION, true));
notifyBlogPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this); notifyLockscreen.setChecked(settings.getBoolean(
notifyLockscreen.setChecked(settings.getBoolean( PREF_NOTIFY_LOCK_SCREEN, false));
PREF_NOTIFY_LOCK_SCREEN, false));
notifySound.setOnPreferenceClickListener( String text;
pref -> onNotificationSoundClicked()); if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
String text; String ringtoneName = settings.get(PREF_NOTIFY_RINGTONE_NAME);
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) { if (StringUtils.isNullOrEmpty(ringtoneName)) {
String ringtoneName = text = getString(R.string.notify_sound_setting_default);
settings.get(PREF_NOTIFY_RINGTONE_NAME);
if (StringUtils.isNullOrEmpty(ringtoneName)) {
text = getString(R.string.notify_sound_setting_default);
} else {
text = ringtoneName;
}
} else { } else {
text = getString(R.string.notify_sound_setting_disabled); text = ringtoneName;
} }
notifySound.setSummary(text);
} else { } else {
setupNotificationPreference(notifyPrivateMessages, text = getString(R.string.notify_sound_setting_disabled);
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);
}); });
} }
@@ -260,41 +266,6 @@ 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));

View File

@@ -1,20 +1,14 @@
package org.briarproject.briar.android.splash; package org.briarproject.briar.android.splash;
import android.content.Intent; import android.app.Activity;
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 AppCompatActivity public class ExpiredActivity extends Activity {
implements OnClickListener {
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -23,13 +17,5 @@ public class ExpiredActivity extends AppCompatActivity
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();
} }
} }

View File

@@ -1,98 +0,0 @@
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;
}
}

View File

@@ -5,6 +5,7 @@ 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;
@@ -25,6 +26,7 @@ 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);
@@ -47,14 +49,30 @@ 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;

View File

@@ -21,12 +21,6 @@ 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";

View File

@@ -0,0 +1,21 @@
<?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>

View File

@@ -1,4 +0,0 @@
<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>

View File

@@ -1,4 +0,0 @@
<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>

View File

@@ -15,35 +15,39 @@
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:baselineAligned="false"> android:weightSum="2">
<LinearLayout <FrameLayout
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" <LinearLayout
android:gravity="center" android:id="@+id/status_container"
android:orientation="vertical" android:layout_width="match_parent"
android:padding="@dimen/margin_medium" android:layout_height="match_parent"
android:visibility="invisible"> android:background="@android:color/background_light"
android:gravity="center"
<ProgressBar android:orientation="vertical"
style="?android:attr/progressBarStyleLarge" android:padding="@dimen/margin_medium"
android:layout_width="wrap_content" android:visibility="invisible">
android:layout_height="wrap_content"/>
<ProgressBar
<TextView style="?android:attr/progressBarStyleLarge"
android:id="@+id/connect_status" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:gravity="center" <TextView
android:paddingTop="@dimen/margin_large" android:id="@+id/connect_status"
tools:text="Connection failed"/> android:layout_width="match_parent"
</LinearLayout> 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"
@@ -55,31 +59,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"/> android:layout_gravity="center"/>
<RelativeLayout <ImageView
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"
<ImageView android:layout_gravity="center"/>
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>
@@ -107,4 +92,5 @@
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>

View File

@@ -1,47 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView <TextView
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:orientation="vertical"> android:layout_gravity="center"
android:gravity="center"
<LinearLayout android:text="@string/expiry_date_reached"
xmlns:android="http://schemas.android.com/apk/res/android" android:textSize="@dimen/text_size_large"/>
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>

View File

@@ -1,170 +0,0 @@
<?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>

View File

@@ -15,35 +15,39 @@
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:baselineAligned="false"> android:weightSum="2">
<LinearLayout <FrameLayout
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" <LinearLayout
android:gravity="center" android:id="@+id/status_container"
android:orientation="vertical" android:layout_width="match_parent"
android:padding="@dimen/margin_medium" android:layout_height="match_parent"
android:visibility="invisible"> android:background="@android:color/background_light"
android:gravity="center"
<ProgressBar android:orientation="vertical"
style="?android:attr/progressBarStyleLarge" android:padding="@dimen/margin_medium"
android:layout_width="wrap_content" android:visibility="invisible">
android:layout_height="wrap_content"/>
<ProgressBar
<TextView style="?android:attr/progressBarStyleLarge"
android:id="@+id/connect_status" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:gravity="center" <TextView
android:paddingTop="@dimen/margin_large" android:id="@+id/connect_status"
tools:text="Connection failed"/> android:layout_width="match_parent"
</LinearLayout> 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"
@@ -55,31 +59,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"/> android:layout_gravity="center"/>
<RelativeLayout <ImageView
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"
<ImageView android:layout_gravity="center"/>
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>
@@ -107,4 +92,5 @@
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>

View File

@@ -1,46 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <RelativeLayout
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="0dp"
android:layout_height="0dp"
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
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_margin="8dp" android:layout_centerInParent="true"/>
android:text="@string/progress_title_logout"
android:textSize="@dimen/text_size_large"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"/>
</android.support.constraint.ConstraintLayout> <TextView
android:id="@+id/title_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar"
android:layout_centerHorizontal="true"
android:paddingTop="@dimen/margin_large"
android:text="@string/progress_title_logout"/>
</RelativeLayout>

View File

@@ -94,25 +94,13 @@
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>

View File

@@ -75,7 +75,9 @@
<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>
@@ -120,6 +122,7 @@
<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>
@@ -172,10 +175,12 @@
<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>
@@ -187,17 +192,23 @@
<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>
@@ -213,14 +224,19 @@
</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_ok">Премахване</string> <string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че искате да премахнете този блог и всички публикации?\nБлогът няма да бъдат премахнат от устройствата на други хора.</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-->
@@ -235,6 +251,8 @@
<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>
@@ -246,8 +264,10 @@
<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_ok">Премахване</string> <string name="blogs_rss_remove_feed_dialog_message">Сигурни ли сте, че искате да премахнете тази емисия и всички нейни публикации?\nВсички споделени от вас публикации няма да бъдат премахнати от устройствата на други хора.</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>

View File

@@ -134,10 +134,8 @@
<!--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>

View File

@@ -33,6 +33,8 @@
<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">
@@ -41,8 +43,6 @@
</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,7 +99,9 @@
<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>
@@ -147,6 +149,7 @@
<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>
@@ -199,11 +202,12 @@
<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">No hi ha publicacions per mostrar</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_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>
@@ -215,17 +219,23 @@
<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>
@@ -241,15 +251,19 @@
</plurals> </plurals>
<string name="nobody">Ningú</string> <string name="nobody">Ningú</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">No hi ha publicacions per mostrar</string> <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="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_ok">Eliminar</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">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-->
@@ -264,6 +278,8 @@
<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>
@@ -275,8 +291,10 @@
<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_ok">Eliminar</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">Elimina el canal</string>
<string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string> <string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string>
<string name="blogs_rss_feeds_manage_empty_state">No heu importat cap canal RSS.\n\nPer què no feu clic al botó més de la part superior dreta per afegir el primer?</string>
<string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string> <string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string>
<!--Settings Network--> <!--Settings Network-->
<string name="network_settings_title">Xarxes</string> <string name="network_settings_title">Xarxes</string>

Some files were not shown because too many files have changed in this diff Show More