Compare commits

..

4 Commits

Author SHA1 Message Date
goapunk
a51f0f803f add bt polling speedtest 2018-05-10 14:42:09 +02:00
akwizgran
8f9d7a70bf Pause between connection attempts. 2018-05-08 14:15:39 +01:00
akwizgran
3ea642c6c0 Don't poll again if last poll is still running. 2018-05-08 13:51:39 +01:00
akwizgran
da0a32c613 Poll contacts in series rather than parallel. 2018-05-08 13:51:31 +01:00
150 changed files with 1525 additions and 2699 deletions

View File

@@ -1,31 +1,27 @@
image: briar/ci-image-android:latest image: registry.gitlab.com/fdroid/ci-images-base:latest
cache:
paths:
- .gradle/wrapper
- .gradle/caches
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
# Download OpenJDK 6 so we can compile against its standard library
- JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb
- if [ ! -d openjdk ]
- then
- wget -q http://ftp.uk.debian.org/debian/pool/main/o/openjdk-6/$JDK_FILE
- dpkg-deb -x $JDK_FILE openjdk
- fi
- export JAVA_6_HOME=$PWD/openjdk/usr/lib/jvm/java-6-openjdk-amd64
test: test:
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
script: script:
- ./gradlew --no-daemon animalSnifferMain animalSnifferTest - ./gradlew test
- ./gradlew --no-daemon test
after_script: after_script:
# these file change every time but should not be cached # this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/ - rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
test_reproducible:
image: briar/reproducer:latest
script:
- cd /opt/briar-reproducer
- ./reproduce.py ${CI_COMMIT_REF_NAME}
only:
- tags

View File

@@ -14,8 +14,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 10005 versionCode 10001
versionName "1.0.5" versionName "1.0.1"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
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.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
@@ -37,14 +38,15 @@ public class AndroidPluginModule {
@Scheduler ScheduledExecutorService scheduler, @Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random, AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory, SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, EventBus eventBus) { Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = DuplexPluginFactory bluetooth =
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, backoffFactory); appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler, DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
appContext, locationUtils, eventBus, torSocketFactory, appContext, locationUtils, reporter, eventBus,
backoffFactory); torSocketFactory, backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
scheduler, backoffFactory, appContext); scheduler, backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =

View File

@@ -32,9 +32,11 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -112,6 +114,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final Backoff backoff; private final Backoff backoff;
private final DuplexPluginCallback callback; private final DuplexPluginCallback callback;
@@ -133,13 +136,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
Context appContext, LocationUtils locationUtils, Context appContext, LocationUtils locationUtils,
SocketFactory torSocketFactory, Backoff backoff, DevReporter reporter, SocketFactory torSocketFactory,
DuplexPluginCallback callback, String architecture, Backoff backoff, DuplexPluginCallback callback,
int maxLatency, int maxIdleTime) { String architecture, int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler; this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoff = backoff; this.backoff = backoff;
this.callback = callback; this.callback = callback;
@@ -385,6 +389,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void sendDevReports() {
ioExecutor.execute(() -> {
// TODO: Trigger this with a TransportEnabledEvent
File reportDir = AndroidUtils.getReportDir(appContext);
reporter.sendReports(reportDir);
});
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it // If there's already a port number stored in config, reuse it
@@ -612,7 +624,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
connectionStatus.getAndSetCircuitBuilt()) { connectionStatus.getAndSetCircuitBuilt()) {
LOG.info("First circuit built"); LOG.info("First circuit built");
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled(); if (isRunning()) {
sendDevReports();
callback.transportEnabled();
}
} }
} }
@@ -641,7 +656,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
connectionStatus.setBootstrapped(); connectionStatus.setBootstrapped();
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled(); if (isRunning()) {
sendDevReports();
callback.transportEnabled();
}
} }
} }

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; 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 org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
@@ -39,18 +40,21 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter;
private final EventBus eventBus; private final EventBus eventBus;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor, public TorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext, ScheduledExecutorService scheduler, Context appContext,
LocationUtils locationUtils, EventBus eventBus, LocationUtils locationUtils, DevReporter reporter,
SocketFactory torSocketFactory, BackoffFactory backoffFactory) { EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler; this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter;
this.eventBus = eventBus; this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
@@ -90,7 +94,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext, TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
locationUtils, torSocketFactory, backoff, callback, locationUtils, reporter, torSocketFactory, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME); architecture, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;

View File

@@ -9,7 +9,6 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.StrictMode;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -67,12 +66,9 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
@Override @Override
protected void writeSeed() { protected void writeSeed() {
// Silence strict mode
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
super.writeSeed(); super.writeSeed();
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18) if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
applyOpenSslFix(); applyOpenSslFix();
StrictMode.setThreadPolicy(tp);
} }
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html // Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html

View File

@@ -1,9 +1,7 @@
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.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
@@ -12,16 +10,11 @@ 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 static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static java.util.logging.Level.INFO;
public class AndroidUtils { public class AndroidUtils {
private static final Logger LOG =
Logger.getLogger(AndroidUtils.class.getName());
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later // Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00"; private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
@@ -42,7 +35,6 @@ public class AndroidUtils {
public static String getBluetoothAddress(Context ctx, public static String getBluetoothAddress(Context ctx,
BluetoothAdapter adapter) { BluetoothAdapter adapter) {
// Return the adapter's address if it's valid and not fake // Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds")
String address = adapter.getAddress(); String address = adapter.getAddress();
if (isValidBluetoothAddress(address)) return address; if (isValidBluetoothAddress(address)) return address;
// Return the address from settings if it's valid and not fake // Return the address from settings if it's valid and not fake
@@ -59,57 +51,17 @@ public class AndroidUtils {
&& !address.equals(FAKE_BLUETOOTH_ADDRESS); && !address.equals(FAKE_BLUETOOTH_ADDRESS);
} }
public static void logDataDirContents(Context ctx) { public static void deleteAppData(Context ctx) {
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of data directory:");
logFileOrDir(new File(ctx.getApplicationInfo().dataDir));
}
}
private static void logFileOrDir(File f) {
LOG.info(f.getAbsolutePath() + " " + f.length());
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children == null) {
LOG.info("Could not list files in " + f.getAbsolutePath());
} else {
for (File child : children) logFileOrDir(child);
}
}
}
public static void deleteAppData(Context ctx, SharedPreferences... clear) {
// Clear and commit shared preferences
for (SharedPreferences prefs : clear) {
boolean cleared = prefs.edit().clear().commit();
if (LOG.isLoggable(INFO)) {
if (cleared) LOG.info("Cleared shared preferences");
else LOG.info("Could not clear shared preferences");
}
}
// Delete files, except lib and shared_prefs directories
File dataDir = new File(ctx.getApplicationInfo().dataDir); File dataDir = new File(ctx.getApplicationInfo().dataDir);
if (LOG.isLoggable(INFO))
LOG.info("Deleting app data from " + dataDir.getAbsolutePath());
File[] children = dataDir.listFiles(); File[] children = dataDir.listFiles();
if (children != null) { if (children != null) {
for (File child : children) { for (File child : children) {
String name = child.getName(); if (!child.getName().equals("lib"))
if (!name.equals("lib") && !name.equals("shared_prefs")) {
if (LOG.isLoggable(INFO))
LOG.info("Deleting " + child.getAbsolutePath());
IoUtils.deleteFileOrDir(child); IoUtils.deleteFileOrDir(child);
}
} }
} else if (LOG.isLoggable(INFO)) {
LOG.info("Could not list files in " + dataDir.getAbsolutePath());
} }
// Recreate the cache dir as some OpenGL drivers expect it to exist // Recreate the cache dir as some OpenGL drivers expect it to exist
boolean recreated = new File(dataDir, "cache").mkdir(); new File(dataDir, "cache").mkdir();
if (LOG.isLoggable(INFO)) {
if (recreated) LOG.info("Recreated cache dir");
else LOG.info("Could not recreate cache dir");
}
} }
public static File getReportDir(Context ctx) { public static File getReportDir(Context ctx) {

View File

@@ -2,7 +2,6 @@ apply plugin: 'java-library'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'witness' apply plugin: 'witness'
dependencies { dependencies {
@@ -15,8 +14,6 @@ dependencies {
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3" testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3" testImplementation "org.hamcrest:hamcrest-core:1.3"
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }
dependencyVerification { dependencyVerification {
@@ -29,9 +26,6 @@ dependencyVerification {
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
@@ -39,7 +33,6 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', 'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
] ]
} }
@@ -55,3 +48,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest testOutput jarTest
} }
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
tasks.withType(JavaCompile) {
useJava6StandardLibrary(it)
}

View File

@@ -45,9 +45,9 @@ public interface ContactManager {
* *
* @param alice true if the local party is Alice * @param alice true if the local party is Alice
*/ */
ContactId addContact(Author remote, AuthorId local, SecretKey master, ContactId addContact(Author remote, AuthorId local,
long timestamp, boolean alice, boolean verified, boolean active) SecretKey master, long timestamp, boolean alice, boolean verified,
throws DbException; boolean active) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* A key pair consisting of a {@link PublicKey} and a {@link PrivateKey}. * A key pair consisting of a {@link PublicKey} and a {@link PrivateKey).
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -8,7 +8,6 @@ import java.io.IOException;
public interface BdfReader { public interface BdfReader {
int DEFAULT_NESTED_LIMIT = 5; int DEFAULT_NESTED_LIMIT = 5;
int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024;
boolean eof() throws IOException; boolean eof() throws IOException;
@@ -40,13 +39,13 @@ public interface BdfReader {
boolean hasString() throws IOException; boolean hasString() throws IOException;
String readString() throws IOException; String readString(int maxLength) throws IOException;
void skipString() throws IOException; void skipString() throws IOException;
boolean hasRaw() throws IOException; boolean hasRaw() throws IOException;
byte[] readRaw() throws IOException; byte[] readRaw(int maxLength) throws IOException;
void skipRaw() throws IOException; void skipRaw() throws IOException;

View File

@@ -9,6 +9,5 @@ public interface BdfReaderFactory {
BdfReader createReader(InputStream in); BdfReader createReader(InputStream in);
BdfReader createReader(InputStream in, int nestedLimit, BdfReader createReader(InputStream in, int nestedLimit);
int maxBufferSize);
} }

View File

@@ -104,12 +104,18 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys, optionally binding them to the given
* key set ID. * contact, and returns a key set ID.
*/ */
KeySetId addTransportKeys(Transaction txn, ContactId c, KeySetId addTransportKeys(Transaction txn, @Nullable ContactId c,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/**
* Binds the given keys for the given transport to the given contact.
*/
void bindTransportKeys(Transaction txn, ContactId c, TransportId t,
KeySetId k) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.

View File

@@ -14,8 +14,6 @@ public interface DatabaseConfig {
File getDatabaseDirectory(); File getDatabaseDirectory();
File getDatabaseKeyDirectory();
void setEncryptionKey(SecretKey key); void setEncryptionKey(SecretKey key);
@Nullable @Nullable

View File

@@ -3,14 +3,10 @@ package org.briarproject.bramble.api.reporting;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
@NotNullByDefault @NotNullByDefault
public interface DevConfig { public interface DevConfig {
PublicKey getDevPublicKey(); PublicKey getDevPublicKey();
String getDevOnionAddress(); String getDevOnionAddress();
File getReportDir();
} }

View File

@@ -23,6 +23,8 @@ public interface DevReporter {
/** /**
* Sends any reports previously stored on disk. * Sends any reports previously stored on disk.
*
* @param reportDir the directory where reports are stored.
*/ */
void sendReports(); void sendReports(File reportDir);
} }

View File

@@ -2,9 +2,9 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
public interface SyncSessionFactory { public interface SyncSessionFactory {
@@ -12,8 +12,8 @@ public interface SyncSessionFactory {
SyncSession createIncomingSession(ContactId c, InputStream in); SyncSession createIncomingSession(ContactId c, InputStream in);
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency, SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
StreamWriter streamWriter); OutputStream out);
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency, SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter); int maxIdleTime, OutputStream out);
} }

View File

@@ -7,12 +7,12 @@ package org.briarproject.bramble.api.system;
public interface Clock { public interface Clock {
/** /**
* @see System#currentTimeMillis() * @see {@link System#currentTimeMillis()}
*/ */
long currentTimeMillis(); long currentTimeMillis();
/** /**
* @see Thread#sleep(long) * @see {@link Thread#sleep(long)}
*/ */
void sleep(long milliseconds) throws InterruptedException; void sleep(long milliseconds) throws InterruptedException;
} }

View File

@@ -19,24 +19,48 @@ 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 a set of transport keys for communicating with the contact over
* each transport and returns the key set IDs. * each transport.
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.
* *
* @param alice true if the local party is Alice * @param alice true if the local party is Alice
* @param active whether the derived keys can be used for outgoing streams
*/ */
Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c, void addContact(Transaction txn, ContactId c, SecretKey master,
SecretKey master, long timestamp, boolean alice, boolean active) long timestamp, boolean alice) throws DbException;
/**
* Derives and stores a set of unbound transport keys for each transport
* and returns the key set IDs.
* <p/>
* The keys must be bound before they can be used for incoming streams,
* and also activated before they can be used for outgoing streams.
*
* @param alice true if the local party is Alice
*/
Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
long timestamp, boolean alice) throws DbException;
/**
* Binds the given transport keys to the given contact.
*/
void bindKeys(Transaction txn, ContactId c, Map<TransportId, KeySetId> keys)
throws DbException; throws DbException;
/** /**
* Marks the given transport keys as usable for outgoing streams. * 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) void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException; 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 * Returns true if we have keys that can be used for outgoing streams to
* the given contact over the given transport. * the given contact over the given transport.

View File

@@ -3,20 +3,23 @@ package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* A set of transport keys for communicating with a contact. * 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 @Immutable
@NotNullByDefault @NotNullByDefault
public class KeySet { public class KeySet {
private final KeySetId keySetId; private final KeySetId keySetId;
@Nullable
private final ContactId contactId; private final ContactId contactId;
private final TransportKeys transportKeys; private final TransportKeys transportKeys;
public KeySet(KeySetId keySetId, ContactId contactId, public KeySet(KeySetId keySetId, @Nullable ContactId contactId,
TransportKeys transportKeys) { TransportKeys transportKeys) {
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
@@ -27,6 +30,7 @@ public class KeySet {
return keySetId; return keySetId;
} }
@Nullable
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.api.transport;
import java.io.IOException;
import java.io.OutputStream;
/**
* An interface for writing data to a transport connection. Data will be
* encrypted and authenticated before being written to the connection.
*/
public interface StreamWriter {
OutputStream getOutputStream();
/**
* Sends the end of stream marker, informing the recipient that no more
* data will be sent. The connection is flushed but not closed.
*/
void sendEndOfStream() throws IOException;
}

View File

@@ -12,12 +12,12 @@ public interface StreamWriterFactory {
* Creates an {@link OutputStream OutputStream} for writing to a * Creates an {@link OutputStream OutputStream} for writing to a
* transport stream * transport stream
*/ */
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates an {@link OutputStream OutputStream} for writing to a contact * Creates an {@link OutputStream OutputStream} for writing to a contact
* exchange stream. * exchange stream.
*/ */
StreamWriter createContactExchangeStreamWriter(OutputStream out, OutputStream createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

View File

@@ -9,37 +9,25 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
@NotNullByDefault @NotNullByDefault
public class IoUtils { public class IoUtils {
private static final Logger LOG = Logger.getLogger(IoUtils.class.getName());
public static void deleteFileOrDir(File f) { public static void deleteFileOrDir(File f) {
if (f.isFile()) { if (f.isFile()) {
delete(f); f.delete();
} else if (f.isDirectory()) { } else if (f.isDirectory()) {
File[] children = f.listFiles(); File[] children = f.listFiles();
if (children != null) if (children != null)
for (File child : children) deleteFileOrDir(child); for (File child : children) deleteFileOrDir(child);
delete(f); f.delete();
} }
} }
private static void delete(File f) { public static void copyAndClose(InputStream in, OutputStream out)
boolean deleted = f.delete(); throws IOException {
if (LOG.isLoggable(INFO)) {
if (deleted) LOG.info("Deleted " + f.getAbsolutePath());
else LOG.info("Could not delete " + f.getAbsolutePath());
}
}
public static void copyAndClose(InputStream in, OutputStream out) {
byte[] buf = new byte[4096]; byte[] buf = new byte[4096];
try { try {
while (true) { while (true) {

View File

@@ -2,7 +2,6 @@ apply plugin: 'java-library'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'net.ltgt.apt' apply plugin: 'net.ltgt.apt'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'witness' apply plugin: 'witness'
@@ -27,8 +26,6 @@ dependencies {
testImplementation "org.hamcrest:hamcrest-core:1.3" testImplementation "org.hamcrest:hamcrest-core:1.3"
testApt 'com.google.dagger:dagger-compiler:2.0.2' testApt 'com.google.dagger:dagger-compiler:2.0.2'
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }
dependencyVerification { dependencyVerification {
@@ -47,9 +44,6 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad', 'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
@@ -58,7 +52,6 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', 'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e', 'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
] ]
@@ -75,3 +68,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest testOutput jarTest
} }
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
tasks.withType(JavaCompile) {
useJava6StandardLibrary(it)
}

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
@@ -29,8 +28,6 @@ public interface BrambleCoreEagerSingletons {
void inject(PropertiesModule.EagerSingletons init); void inject(PropertiesModule.EagerSingletons init);
void inject(ReportingModule.EagerSingletons init);
void inject(SyncModule.EagerSingletons init); void inject(SyncModule.EagerSingletons init);
void inject(SystemModule.EagerSingletons init); void inject(SystemModule.EagerSingletons init);

View File

@@ -59,7 +59,6 @@ public class BrambleCoreModule {
c.inject(new LifecycleModule.EagerSingletons()); c.inject(new LifecycleModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());
c.inject(new ReportingModule.EagerSingletons());
c.inject(new SyncModule.EagerSingletons()); c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons()); c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());

View File

@@ -30,7 +30,6 @@ import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.EOFException; import java.io.EOFException;
@@ -153,11 +152,11 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
recordReaderFactory.createRecordReader(streamReader); recordReaderFactory.createRecordReader(streamReader);
// Create the writers // Create the writers
StreamWriter streamWriter = OutputStream streamWriter =
streamWriterFactory.createContactExchangeStreamWriter(out, streamWriterFactory.createContactExchangeStreamWriter(out,
alice ? aliceHeaderKey : bobHeaderKey); alice ? aliceHeaderKey : bobHeaderKey);
RecordWriter recordWriter = RecordWriter recordWriter =
recordWriterFactory.createRecordWriter(streamWriter.getOutputStream()); recordWriterFactory.createRecordWriter(streamWriter);
// Derive the nonces to be signed // Derive the nonces to be signed
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret, byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
@@ -185,8 +184,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
localSignature, localTimestamp); localSignature, localTimestamp);
recordWriter.flush(); recordWriter.flush();
} }
// Send EOF on the outgoing stream // Close the outgoing stream
streamWriter.sendEndOfStream(); recordWriter.close();
// Skip any remaining records from the incoming stream // Skip any remaining records from the incoming stream
try { try {
while (true) recordReader.readRecord(); while (true) recordReader.readRecord();

View File

@@ -46,7 +46,7 @@ class ContactManagerImpl implements ContactManager {
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, active); 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 (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c; return c;

View File

@@ -152,47 +152,59 @@ public class MessageEncrypter {
} }
} }
public static void main(String[] args) { public static void main(String[] args) throws Exception {
if (args.length < 1) { if (args.length < 1) {
printUsage(); printUsage();
System.exit(1); return;
} }
SecureRandom random = new SecureRandom();
MessageEncrypter encrypter = new MessageEncrypter(random);
if (args[0].equals("generate")) { if (args[0].equals("generate")) {
if (args.length != 3) { if (args.length != 3) {
printUsage(); printUsage();
System.exit(1); return;
}
try {
generateKeyPair(args[1], args[2]);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
} }
// Generate a key pair
KeyPair keyPair = encrypter.generateKeyPair();
PrintStream out = new PrintStream(new FileOutputStream(args[1]));
out.print(
StringUtils.toHexString(keyPair.getPublic().getEncoded()));
out.flush();
out.close();
out = new PrintStream(new FileOutputStream(args[2]));
out.print(
StringUtils.toHexString(keyPair.getPrivate().getEncoded()));
out.flush();
out.close();
} else if (args[0].equals("encrypt")) { } else if (args[0].equals("encrypt")) {
if (args.length != 2) { if (args.length != 2) {
printUsage(); printUsage();
System.exit(1); return;
}
try {
encryptMessage(args[1]);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
} }
// Encrypt a decrypted message
InputStream in = new FileInputStream(args[1]);
byte[] keyBytes = StringUtils.fromHexString(readFully(in).trim());
PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8"));
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
} else if (args[0].equals("decrypt")) { } else if (args[0].equals("decrypt")) {
if (args.length != 2) { if (args.length != 2) {
printUsage(); printUsage();
System.exit(1); return;
}
try {
decryptMessage(args[1]);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
} }
// Decrypt an encrypted message
InputStream in = new FileInputStream(args[1]);
byte[] keyBytes = StringUtils.fromHexString(readFully(in).trim());
PrivateKey privateKey =
encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, Charset.forName("UTF-8")));
} else { } else {
printUsage(); printUsage();
System.exit(1);
} }
} }
@@ -204,46 +216,6 @@ public class MessageEncrypter {
System.err.println("MessageEncrypter decrypt <private_key_file>"); System.err.println("MessageEncrypter decrypt <private_key_file>");
} }
private static void generateKeyPair(String publicKeyFile,
String privateKeyFile) throws Exception {
SecureRandom random = new SecureRandom();
MessageEncrypter encrypter = new MessageEncrypter(random);
KeyPair keyPair = encrypter.generateKeyPair();
PrintStream out = new PrintStream(new FileOutputStream(publicKeyFile));
out.print(StringUtils.toHexString(keyPair.getPublic().getEncoded()));
out.flush();
out.close();
out = new PrintStream(new FileOutputStream(privateKeyFile));
out.print(StringUtils.toHexString(keyPair.getPrivate().getEncoded()));
out.flush();
out.close();
}
private static void encryptMessage(String publicKeyFile) throws Exception {
SecureRandom random = new SecureRandom();
MessageEncrypter encrypter = new MessageEncrypter(random);
InputStream in = new FileInputStream(publicKeyFile);
byte[] keyBytes = StringUtils.fromHexString(readFully(in).trim());
PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8"));
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
}
private static void decryptMessage(String privateKeyFile) throws Exception {
SecureRandom random = new SecureRandom();
MessageEncrypter encrypter = new MessageEncrypter(random);
InputStream in = new FileInputStream(privateKeyFile);
byte[] keyBytes = StringUtils.fromHexString(readFully(in).trim());
PrivateKey privateKey =
encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, Charset.forName("UTF-8")));
}
private static String readFully(InputStream in) throws IOException { private static String readFully(InputStream in) throws IOException {
String newline = System.getProperty("line.separator"); String newline = System.getProperty("line.separator");
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();

View File

@@ -10,37 +10,37 @@ import java.security.GeneralSecurityException;
interface Signature { interface Signature {
/** /**
* @see java.security.Signature#initSign(java.security.PrivateKey) * @see {@link java.security.Signature#initSign(java.security.PrivateKey)}
*/ */
void initSign(PrivateKey k) throws GeneralSecurityException; void initSign(PrivateKey k) throws GeneralSecurityException;
/** /**
* @see java.security.Signature#initVerify(java.security.PublicKey) * @see {@link java.security.Signature#initVerify(java.security.PublicKey)}
*/ */
void initVerify(PublicKey k) throws GeneralSecurityException; void initVerify(PublicKey k) throws GeneralSecurityException;
/** /**
* @see java.security.Signature#update(byte) * @see {@link java.security.Signature#update(byte)}
*/ */
void update(byte b) throws GeneralSecurityException; void update(byte b) throws GeneralSecurityException;
/** /**
* @see java.security.Signature#update(byte[]) * @see {@link java.security.Signature#update(byte[])}
*/ */
void update(byte[] b) throws GeneralSecurityException; void update(byte[] b) throws GeneralSecurityException;
/** /**
* @see java.security.Signature#update(byte[], int, int) * @see {@link java.security.Signature#update(byte[], int, int)}
*/ */
void update(byte[] b, int off, int len) throws GeneralSecurityException; void update(byte[] b, int off, int len) throws GeneralSecurityException;
/** /**
* @see java.security.Signature#sign()} * @see {@link java.security.Signature#sign()}
*/ */
byte[] sign() throws GeneralSecurityException; byte[] sign() throws GeneralSecurityException;
/** /**
* @see java.security.Signature#verify(byte[]) * @see {@link java.security.Signature#verify(byte[])}
*/ */
boolean verify(byte[] signature) throws GeneralSecurityException; boolean verify(byte[] signature) throws GeneralSecurityException;
} }

View File

@@ -8,7 +8,6 @@ import java.io.InputStream;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_MAX_BUFFER_SIZE;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_NESTED_LIMIT; import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_NESTED_LIMIT;
@Immutable @Immutable
@@ -17,13 +16,11 @@ class BdfReaderFactoryImpl implements BdfReaderFactory {
@Override @Override
public BdfReader createReader(InputStream in) { public BdfReader createReader(InputStream in) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT);
DEFAULT_MAX_BUFFER_SIZE);
} }
@Override @Override
public BdfReader createReader(InputStream in, int nestedLimit, public BdfReader createReader(InputStream in, int nestedLimit) {
int maxBufferSize) { return new BdfReaderImpl(in, nestedLimit);
return new BdfReaderImpl(in, nestedLimit, maxBufferSize);
} }
} }

View File

@@ -37,16 +37,15 @@ class BdfReaderImpl implements BdfReader {
private static final byte[] EMPTY_BUFFER = new byte[0]; private static final byte[] EMPTY_BUFFER = new byte[0];
private final InputStream in; private final InputStream in;
private final int nestedLimit, maxBufferSize; private final int nestedLimit;
private boolean hasLookahead = false, eof = false; private boolean hasLookahead = false, eof = false;
private byte next; private byte next;
private byte[] buf = new byte[8]; private byte[] buf = new byte[8];
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize) { BdfReaderImpl(InputStream in, int nestedLimit) {
this.in = in; this.in = in;
this.nestedLimit = nestedLimit; this.nestedLimit = nestedLimit;
this.maxBufferSize = maxBufferSize;
} }
private void readLookahead() throws IOException { private void readLookahead() throws IOException {
@@ -92,8 +91,8 @@ class BdfReaderImpl implements BdfReader {
if (hasBoolean()) return readBoolean(); if (hasBoolean()) return readBoolean();
if (hasLong()) return readLong(); if (hasLong()) return readLong();
if (hasDouble()) return readDouble(); if (hasDouble()) return readDouble();
if (hasString()) return readString(); if (hasString()) return readString(Integer.MAX_VALUE);
if (hasRaw()) return readRaw(); if (hasRaw()) return readRaw(Integer.MAX_VALUE);
if (hasList()) return readList(level); if (hasList()) return readList(level);
if (hasDictionary()) return readDictionary(level); if (hasDictionary()) return readDictionary(level);
throw new FormatException(); throw new FormatException();
@@ -246,11 +245,11 @@ class BdfReaderImpl implements BdfReader {
} }
@Override @Override
public String readString() throws IOException { public String readString(int maxLength) throws IOException {
if (!hasString()) throw new FormatException(); if (!hasString()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
int length = readStringLength(); int length = readStringLength();
if (length < 0 || length > maxBufferSize) throw new FormatException(); if (length < 0 || length > maxLength) throw new FormatException();
if (length == 0) return ""; if (length == 0) return "";
readIntoBuffer(length); readIntoBuffer(length);
return new String(buf, 0, length, "UTF-8"); return new String(buf, 0, length, "UTF-8");
@@ -280,11 +279,11 @@ class BdfReaderImpl implements BdfReader {
} }
@Override @Override
public byte[] readRaw() throws IOException { public byte[] readRaw(int maxLength) throws IOException {
if (!hasRaw()) throw new FormatException(); if (!hasRaw()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
int length = readRawLength(); int length = readRawLength();
if (length < 0 || length > maxBufferSize) throw new FormatException(); if (length < 0 || length > maxLength) throw new FormatException();
if (length == 0) return EMPTY_BUFFER; if (length == 0) return EMPTY_BUFFER;
byte[] b = new byte[length]; byte[] b = new byte[length];
readIntoBuffer(b, length); readIntoBuffer(b, length);
@@ -382,7 +381,7 @@ class BdfReaderImpl implements BdfReader {
BdfDictionary dictionary = new BdfDictionary(); BdfDictionary dictionary = new BdfDictionary();
readDictionaryStart(); readDictionaryStart();
while (!hasDictionaryEnd()) while (!hasDictionaryEnd())
dictionary.put(readString(), readObject(level + 1)); dictionary.put(readString(Integer.MAX_VALUE), readObject(level + 1));
readDictionaryEnd(); readDictionaryEnd();
return dictionary; return dictionary;
} }

View File

@@ -59,8 +59,8 @@ class MetadataParserImpl implements MetadataParser {
if (reader.hasBoolean()) return reader.readBoolean(); if (reader.hasBoolean()) return reader.readBoolean();
if (reader.hasLong()) return reader.readLong(); if (reader.hasLong()) return reader.readLong();
if (reader.hasDouble()) return reader.readDouble(); if (reader.hasDouble()) return reader.readDouble();
if (reader.hasString()) return reader.readString(); if (reader.hasString()) return reader.readString(Integer.MAX_VALUE);
if (reader.hasRaw()) return reader.readRaw(); if (reader.hasRaw()) return reader.readRaw(Integer.MAX_VALUE);
if (reader.hasList()) return reader.readList(); if (reader.hasList()) return reader.readList();
if (reader.hasDictionary()) return reader.readDictionary(); if (reader.hasDictionary()) return reader.readDictionary();
throw new FormatException(); throw new FormatException();

View File

@@ -34,8 +34,8 @@ import javax.annotation.Nullable;
* A low-level interface to the database (DatabaseComponent provides a * A low-level interface to the database (DatabaseComponent provides a
* high-level interface). Most operations take a transaction argument, which is * high-level interface). Most operations take a transaction argument, which is
* obtained by calling {@link #startTransaction()}. Every transaction must be * obtained by calling {@link #startTransaction()}. Every transaction must be
* terminated by calling either {@link #abortTransaction(Object) abortTransaction(T)} or * terminated by calling either {@link #abortTransaction(T)} or
* {@link #commitTransaction(Object) commitTransaction(T)}, even if an exception is thrown. * {@link #commitTransaction(T)}, even if an exception is thrown.
*/ */
@NotNullByDefault @NotNullByDefault
interface Database<T> { interface Database<T> {
@@ -125,10 +125,16 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Stores the given transport keys for the given contact and returns a * Stores the given transport keys, optionally binding them to the given
* key set ID. * contact, and returns a key set ID.
*/ */
KeySetId addTransportKeys(T txn, ContactId c, TransportKeys k) KeySetId addTransportKeys(T txn, @Nullable ContactId c, TransportKeys k)
throws DbException;
/**
* Binds the given keys for the given transport to the given contact.
*/
void bindTransportKeys(T txn, ContactId c, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**

View File

@@ -234,17 +234,29 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public KeySetId addTransportKeys(Transaction transaction, ContactId c, public KeySetId addTransportKeys(Transaction transaction,
TransportKeys k) throws DbException { @Nullable ContactId c, TransportKeys k) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (c != null && !db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
if (!db.containsTransport(txn, k.getTransportId())) if (!db.containsTransport(txn, k.getTransportId()))
throw new NoSuchTransportException(); throw new NoSuchTransportException();
return db.addTransportKeys(txn, c, k); 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();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
if (!db.containsTransport(txn, t))
throw new NoSuchTransportException();
db.bindTransportKeys(txn, c, t, k);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {

View File

@@ -53,7 +53,6 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.sql.Types.INTEGER; import static java.sql.Types.INTEGER;
import static java.util.Collections.singletonList;
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;
@@ -75,7 +74,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 39; static final int CODE_SCHEMA_VERSION = 38;
// Rotation period offsets for incoming transport keys // Rotation period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -237,7 +236,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (transportId _STRING NOT NULL," + " (transportId _STRING NOT NULL,"
+ " keySetId _COUNTER," + " keySetId _COUNTER,"
+ " rotationPeriod BIGINT NOT NULL," + " rotationPeriod BIGINT NOT NULL,"
+ " contactId INT 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,"
+ " stream BIGINT NOT NULL," + " stream BIGINT NOT NULL,"
@@ -256,7 +255,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (transportId _STRING NOT NULL," + " (transportId _STRING NOT NULL,"
+ " keySetId INT NOT NULL," + " keySetId INT NOT NULL,"
+ " rotationPeriod BIGINT NOT NULL," + " rotationPeriod BIGINT NOT NULL,"
+ " contactId INT 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,"
@@ -390,7 +389,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
List<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {
return singletonList(new Migration38_39()); return Collections.emptyList();
} }
private void storeSchemaVersion(Connection txn, int version) private void storeSchemaVersion(Connection txn, int version)
@@ -884,7 +883,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public KeySetId addTransportKeys(Connection txn, ContactId c, public KeySetId addTransportKeys(Connection txn, @Nullable ContactId c,
TransportKeys k) throws DbException { TransportKeys k) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -894,7 +893,8 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " rotationPeriod, tagKey, headerKey, stream, active)" + " rotationPeriod, tagKey, headerKey, stream, active)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); if (c == null) ps.setNull(1, INTEGER);
else ps.setInt(1, c.getInt());
ps.setString(2, k.getTransportId().getString()); ps.setString(2, k.getTransportId().getString());
OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
ps.setLong(3, outCurr.getRotationPeriod()); ps.setLong(3, outCurr.getRotationPeriod());
@@ -922,7 +922,8 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, keySetId.getInt()); ps.setInt(1, keySetId.getInt());
ps.setInt(2, c.getInt()); if (c == null) ps.setNull(2, INTEGER);
else ps.setInt(2, c.getInt());
ps.setString(3, k.getTransportId().getString()); ps.setString(3, k.getTransportId().getString());
// Previous rotation period // Previous rotation period
IncomingKeys inPrev = k.getPreviousIncomingKeys(); IncomingKeys inPrev = k.getPreviousIncomingKeys();
@@ -964,6 +965,33 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void bindTransportKeys(Connection txn, ContactId c, TransportId t,
KeySetId k) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE outgoingKeys SET contactId = ?"
+ " WHERE keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, k.getInt());
int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
sql = "UPDATE incomingKeys SET contactId = ?"
+ " WHERE keySetId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, k.getInt());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
@Override @Override
public boolean containsContact(Connection txn, AuthorId remote, public boolean containsContact(Connection txn, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -2144,6 +2172,7 @@ abstract class JdbcDatabase implements Database<Connection> {
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)); KeySetId keySetId = new KeySetId(rs.getInt(1));
ContactId contactId = new ContactId(rs.getInt(2)); ContactId contactId = new ContactId(rs.getInt(2));
if (rs.wasNull()) contactId = null;
long rotationPeriod = rs.getLong(3); long rotationPeriod = rs.getLong(3);
SecretKey tagKey = new SecretKey(rs.getBytes(4)); SecretKey tagKey = new SecretKey(rs.getBytes(4));
SecretKey headerKey = new SecretKey(rs.getBytes(5)); SecretKey headerKey = new SecretKey(rs.getBytes(5));

View File

@@ -1,54 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
class Migration38_39 implements Migration<Connection> {
private static final Logger LOG =
Logger.getLogger(Migration38_39.class.getName());
@Override
public int getStartVersion() {
return 38;
}
@Override
public int getEndVersion() {
return 39;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
// Add not null constraints
s.execute("ALTER TABLE outgoingKeys"
+ " ALTER COLUMN contactId"
+ " SET NOT NULL");
s.execute("ALTER TABLE incomingKeys"
+ " ALTER COLUMN contactId"
+ " SET NOT NULL");
} catch (SQLException e) {
tryToClose(s);
throw new DbException(e);
}
}
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -14,12 +14,12 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -101,7 +101,7 @@ class ConnectionManagerImpl implements ConnectionManager {
private SyncSession createSimplexOutgoingSession(StreamContext ctx, private SyncSession createSimplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w) throws IOException { TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( OutputStream streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
return syncSessionFactory.createSimplexOutgoingSession( return syncSessionFactory.createSimplexOutgoingSession(
ctx.getContactId(), w.getMaxLatency(), streamWriter); ctx.getContactId(), w.getMaxLatency(), streamWriter);
@@ -109,7 +109,7 @@ class ConnectionManagerImpl implements ConnectionManager {
private SyncSession createDuplexOutgoingSession(StreamContext ctx, private SyncSession createDuplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w) throws IOException { TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( OutputStream streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
return syncSessionFactory.createDuplexOutgoingSession( return syncSessionFactory.createDuplexOutgoingSession(
ctx.getContactId(), w.getMaxLatency(), w.getMaxIdleTime(), ctx.getContactId(), w.getMaxLatency(), w.getMaxIdleTime(),
@@ -300,8 +300,8 @@ class ConnectionManagerImpl implements ConnectionManager {
} }
private void disposeReader(boolean exception, boolean recognised) { private void disposeReader(boolean exception, boolean recognised) {
// Interrupt the outgoing session so it finishes cleanly if (exception && outgoingSession != null)
if (outgoingSession != null) outgoingSession.interrupt(); outgoingSession.interrupt();
try { try {
reader.dispose(exception, recognised); reader.dispose(exception, recognised);
} catch (IOException e) { } catch (IOException e) {
@@ -310,8 +310,6 @@ class ConnectionManagerImpl implements ConnectionManager {
} }
private void disposeWriter(boolean exception) { private void disposeWriter(boolean exception) {
// Interrupt the incoming session if an exception occurred,
// otherwise wait for the end of stream marker
if (exception && incomingSession != null) if (exception && incomingSession != null)
incomingSession.interrupt(); incomingSession.interrupt();
try { try {
@@ -409,8 +407,8 @@ class ConnectionManagerImpl implements ConnectionManager {
} }
private void disposeReader(boolean exception, boolean recognised) { private void disposeReader(boolean exception, boolean recognised) {
// Interrupt the outgoing session so it finishes cleanly if (exception && outgoingSession != null)
if (outgoingSession != null) outgoingSession.interrupt(); outgoingSession.interrupt();
try { try {
reader.dispose(exception, recognised); reader.dispose(exception, recognised);
} catch (IOException e) { } catch (IOException e) {
@@ -419,8 +417,6 @@ class ConnectionManagerImpl implements ConnectionManager {
} }
private void disposeWriter(boolean exception) { private void disposeWriter(boolean exception) {
// Interrupt the incoming session if an exception occurred,
// otherwise wait for the end of stream marker
if (exception && incomingSession != null) if (exception && incomingSession != null)
incomingSession.interrupt(); incomingSession.interrupt();
try { try {

View File

@@ -106,7 +106,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
if (m == null) return Collections.emptyList(); if (m == null) return Collections.emptyList();
List<ContactId> ids = new ArrayList<>(m.keySet()); List<ContactId> ids = new ArrayList<>(m.keySet());
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info(ids.size() + " contacts connected: " + t); LOG.info(ids.size() + " contacts connected");
return ids; return ids;
} finally { } finally {
lock.unlock(); lock.unlock();

View File

@@ -24,7 +24,9 @@ import org.briarproject.bramble.api.system.Scheduler;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -53,6 +55,7 @@ class Poller implements EventListener {
private final Clock clock; private final Clock clock;
private final Lock lock; private final Lock lock;
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
private final Set<TransportId> polling; // Locking: lock
@Inject @Inject
Poller(@IoExecutor Executor ioExecutor, Poller(@IoExecutor Executor ioExecutor,
@@ -69,16 +72,19 @@ class Poller implements EventListener {
this.clock = clock; this.clock = clock;
lock = new ReentrantLock(); lock = new ReentrantLock();
tasks = new HashMap<>(); tasks = new HashMap<>();
polling = new HashSet<>();
} }
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactStatusChangedEvent) { if (e instanceof ContactStatusChangedEvent) {
ContactStatusChangedEvent c = (ContactStatusChangedEvent) e; ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
/*
if (c.isActive()) { if (c.isActive()) {
// Connect to the newly activated contact // Connect to the newly activated contact
connectToContact(c.getContactId()); connectToContact(c.getContactId());
} }
*/
} else if (e instanceof ConnectionClosedEvent) { } else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e; ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased // Reschedule polling, the polling interval may have decreased
@@ -215,20 +221,33 @@ class Poller implements EventListener {
@Override @Override
@IoExecutor @IoExecutor
public void run() { public void run() {
TransportId t = plugin.getId();
boolean shouldPoll;
lock.lock(); lock.lock();
try { try {
TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t); ScheduledPollTask scheduled = tasks.get(t);
if (scheduled != null && scheduled.task != this) if (scheduled != null && scheduled.task != this)
return; // Replaced by another task return; // Replaced by another task
tasks.remove(t); tasks.remove(t);
// Don't poll again if last poll is still running
shouldPoll = polling.add(t);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
int delay = plugin.getPollingInterval(); int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble()); if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false); schedule(plugin, delay, false);
poll(plugin); if (shouldPoll) {
poll(plugin);
} else if (LOG.isLoggable(INFO)) {
LOG.info("Last poll for " + t + " is still running");
}
lock.lock();
try {
polling.remove(t);
} finally {
lock.unlock();
}
} }
} }
} }

View File

@@ -26,9 +26,11 @@ import org.briarproject.bramble.util.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -53,6 +55,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName()); Logger.getLogger(BluetoothPlugin.class.getName());
/**
* How many milliseconds to pause between connection attempts when
* polling, to avoid interfering with other Bluetooth or wifi connections.
*/
private static final int POLLING_PAUSE_MS = 3000;
final BluetoothConnectionLimiter connectionLimiter; final BluetoothConnectionLimiter connectionLimiter;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -253,27 +261,42 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public void poll(Collection<ContactId> connected) { public void poll(Collection<ContactId> connected) {
if (!isRunning() || !shouldAllowContactConnections()) return; if (!isRunning() || !shouldAllowContactConnections()) return;
backoff.increment(); backoff.increment();
// Try to connect to known devices in parallel // Try to connect to known devices in a random order
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties(); callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { List<ContactId> keys = new ArrayList<>(remote.keySet());
ContactId c = e.getKey(); Collections.shuffle(keys);
if (connected.contains(c)) continue; ioExecutor.execute(() -> {
String address = e.getValue().get(PROP_ADDRESS); boolean first = true;
if (StringUtils.isNullOrEmpty(address)) continue; for (ContactId c : keys) {
String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionLimiter.canOpenContactConnection()) return; if (!connectionLimiter.canOpenContactConnection()) return;
if (connected.contains(c)) continue;
TransportProperties p = remote.get(c);
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
// Pause between connection attempts
if (first) {
first = false;
} else {
try {
Thread.sleep(POLLING_PAUSE_MS);
} catch (InterruptedException ex) {
LOG.info("Interrupted while polling");
Thread.currentThread().interrupt();
return;
}
}
DuplexTransportConnection conn = connect(address, uuid); DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) { if (conn != null) {
backoff.reset(); backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn)) if (connectionLimiter.contactConnectionOpened(conn))
callback.outgoingConnectionCreated(c, conn); callback.outgoingConnectionCreated(c, conn);
} }
}); }
} });
} }
@Nullable @Nullable

View File

@@ -1,12 +1,7 @@
package org.briarproject.bramble.reporting; package org.briarproject.bramble.reporting;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
@@ -23,19 +18,17 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class DevReporterImpl implements DevReporter, EventListener { class DevReporterImpl implements DevReporter {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(DevReporterImpl.class.getName()); Logger.getLogger(DevReporterImpl.class.getName());
@@ -43,15 +36,12 @@ class DevReporterImpl implements DevReporter, EventListener {
private static final int SOCKET_TIMEOUT = 30 * 1000; // 30 seconds private static final int SOCKET_TIMEOUT = 30 * 1000; // 30 seconds
private static final int LINE_LENGTH = 70; private static final int LINE_LENGTH = 70;
private final Executor ioExecutor;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final DevConfig devConfig; private final DevConfig devConfig;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
@Inject DevReporterImpl(CryptoComponent crypto, DevConfig devConfig,
DevReporterImpl(@IoExecutor Executor ioExecutor, CryptoComponent crypto, SocketFactory torSocketFactory) {
DevConfig devConfig, SocketFactory torSocketFactory) {
this.ioExecutor = ioExecutor;
this.crypto = crypto; this.crypto = crypto;
this.devConfig = devConfig; this.devConfig = devConfig;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
@@ -73,7 +63,6 @@ class DevReporterImpl implements DevReporter, EventListener {
@Override @Override
public void encryptReportToFile(File reportDir, String filename, public void encryptReportToFile(File reportDir, String filename,
String report) throws FileNotFoundException { String report) throws FileNotFoundException {
LOG.info("Encrypting report to file");
byte[] plaintext = StringUtils.toUtf8(report); byte[] plaintext = StringUtils.toUtf8(report);
byte[] ciphertext = crypto.encryptToKey(devConfig.getDevPublicKey(), byte[] ciphertext = crypto.encryptToKey(devConfig.getDevPublicKey(),
plaintext); plaintext);
@@ -93,17 +82,7 @@ class DevReporterImpl implements DevReporter, EventListener {
} }
@Override @Override
public void eventOccurred(Event e) { public void sendReports(File reportDir) {
if (e instanceof TransportEnabledEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e;
if (t.getTransportId().equals(TorConstants.ID))
ioExecutor.execute(this::sendReports);
}
}
@Override
public void sendReports() {
File reportDir = devConfig.getReportDir();
File[] reports = reportDir.listFiles(); File[] reports = reportDir.listFiles();
if (reports == null || reports.length == 0) if (reports == null || reports.length == 0)
return; // No reports to send return; // No reports to send

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.reporting; package org.briarproject.bramble.reporting;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import javax.inject.Inject; import javax.net.SocketFactory;
import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -12,16 +12,9 @@ import dagger.Provides;
@Module @Module
public class ReportingModule { public class ReportingModule {
public static class EagerSingletons {
@Inject
DevReporter devReporter;
}
@Provides @Provides
@Singleton DevReporter provideDevReporter(CryptoComponent crypto,
DevReporter provideDevReporter(DevReporterImpl devReporter, DevConfig devConfig, SocketFactory torSocketFactory) {
EventBus eventBus) { return new DevReporterImpl(crypto, devConfig, torSocketFactory);
eventBus.addListener(devReporter);
return devReporter;
} }
} }

View File

@@ -23,7 +23,6 @@ import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.sync.event.MessageToAckEvent; 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.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@@ -68,7 +67,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final Clock clock; private final Clock clock;
private final ContactId contactId; private final ContactId contactId;
private final int maxLatency, maxIdleTime; private final int maxLatency, maxIdleTime;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -83,8 +81,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency, EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
int maxIdleTime, StreamWriter streamWriter, int maxIdleTime, SyncRecordWriter recordWriter) {
SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -92,7 +89,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
this.contactId = contactId; this.contactId = contactId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.streamWriter = streamWriter;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
writerTasks = new LinkedBlockingQueue<>(); writerTasks = new LinkedBlockingQueue<>();
} }
@@ -153,7 +149,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
dataToFlush = true; dataToFlush = true;
} }
} }
streamWriter.sendEndOfStream(); if (dataToFlush) recordWriter.flush();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted while waiting for a record to write"); LOG.info("Interrupted while waiting for a record to write");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -63,11 +63,7 @@ class IncomingSession implements SyncSession, EventListener {
eventBus.addListener(this); eventBus.addListener(this);
try { try {
// Read records until interrupted or EOF // Read records until interrupted or EOF
while (!interrupted) { while (!interrupted && !recordReader.eof()) {
if (recordReader.eof()) {
LOG.info("End of stream");
return;
}
if (recordReader.hasAck()) { if (recordReader.hasAck()) {
Ack a = recordReader.readAck(); Ack a = recordReader.readAck();
dbExecutor.execute(new ReceiveAck(a)); dbExecutor.execute(new ReceiveAck(a));

View File

@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@@ -52,7 +51,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus; private final EventBus eventBus;
private final ContactId contactId; private final ContactId contactId;
private final int maxLatency; private final int maxLatency;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -60,14 +58,13 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private volatile boolean interrupted = false; private volatile boolean interrupted = false;
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, int maxLatency, EventBus eventBus, ContactId contactId,
StreamWriter streamWriter, SyncRecordWriter recordWriter) { int maxLatency, SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
this.contactId = contactId; this.contactId = contactId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.streamWriter = streamWriter;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record outstandingQueries = new AtomicInteger(2); // One per type of record
writerTasks = new LinkedBlockingQueue<>(); writerTasks = new LinkedBlockingQueue<>();
@@ -88,7 +85,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
if (task == CLOSE) break; if (task == CLOSE) break;
task.run(); task.run();
} }
streamWriter.sendEndOfStream(); recordWriter.flush();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted while waiting for a record to write"); LOG.info("Interrupted while waiting for a record to write");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -54,21 +53,19 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, public SyncSession createSimplexOutgoingSession(ContactId c,
int maxLatency, StreamWriter streamWriter) { int maxLatency, OutputStream out) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out); recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
maxLatency, streamWriter, recordWriter); maxLatency, recordWriter);
} }
@Override @Override
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency, public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter) { int maxIdleTime, OutputStream out) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out); recordWriterFactory.createRecordWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
maxLatency, maxIdleTime, streamWriter, recordWriter); maxLatency, maxIdleTime, recordWriter);
} }
} }

View File

@@ -99,18 +99,39 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c, public void addContact(Transaction txn, ContactId c, SecretKey master,
SecretKey master, long timestamp, boolean alice, boolean active) long timestamp, boolean alice) throws DbException {
for (TransportKeyManager m : managers.values())
m.addContact(txn, c, master, timestamp, alice);
}
@Override
public Map<TransportId, KeySetId> addUnboundKeys(Transaction txn,
SecretKey master, long timestamp, boolean alice)
throws DbException { throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = e.getValue(); TransportKeyManager m = e.getValue();
ids.put(t, m.addContact(txn, c, master, timestamp, alice, active)); ids.put(t, m.addUnboundKeys(txn, master, timestamp, alice));
} }
return ids; 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 @Override
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys) public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException { throws DbException {
@@ -125,10 +146,24 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
} }
@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 @Override
public boolean canSendOutgoingStreams(ContactId c, TransportId t) { public boolean canSendOutgoingStreams(ContactId c, TransportId t) {
TransportKeyManager m = managers.get(t); TransportKeyManager m = managers.get(t);
return m != null && m.canSendOutgoingStreams(c); return m == null ? false : m.canSendOutgoingStreams(c);
} }
@Override @Override

View File

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

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.OutputStream; import java.io.OutputStream;
@@ -24,14 +23,14 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
} }
@Override @Override
public StreamWriter createStreamWriter(OutputStream out, public OutputStream createStreamWriter(OutputStream out,
StreamContext ctx) { StreamContext ctx) {
return new StreamWriterImpl( return new StreamWriterImpl(
streamEncrypterFactory.createStreamEncrypter(out, ctx)); streamEncrypterFactory.createStreamEncrypter(out, ctx));
} }
@Override @Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out, public OutputStream createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamWriterImpl( return new StreamWriterImpl(
streamEncrypterFactory.createContactExchangeStreamDecrypter(out, streamEncrypterFactory.createContactExchangeStreamDecrypter(out,

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamEncrypter; import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@@ -18,7 +17,7 @@ import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYL
*/ */
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class StreamWriterImpl extends OutputStream implements StreamWriter { class StreamWriterImpl extends OutputStream {
private final StreamEncrypter encrypter; private final StreamEncrypter encrypter;
private final byte[] payload; private final byte[] payload;
@@ -30,17 +29,6 @@ class StreamWriterImpl extends OutputStream implements StreamWriter {
payload = new byte[MAX_PAYLOAD_LENGTH]; payload = new byte[MAX_PAYLOAD_LENGTH];
} }
@Override
public OutputStream getOutputStream() {
return this;
}
@Override
public void sendEndOfStream() throws IOException {
writeFrame(true);
encrypter.flush();
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
writeFrame(true); writeFrame(true);

View File

@@ -15,11 +15,18 @@ interface TransportKeyManager {
void start(Transaction txn) throws DbException; void start(Transaction txn) throws DbException;
KeySetId addContact(Transaction txn, ContactId c, SecretKey master, void addContact(Transaction txn, ContactId c, SecretKey master,
long timestamp, boolean alice, boolean active) 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 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); boolean canSendOutgoingStreams(ContactId c);

View File

@@ -28,6 +28,7 @@ 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;
@@ -118,14 +119,16 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
// Locking: lock // Locking: lock
private void addKeys(KeySetId keySetId, ContactId contactId, private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
MutableTransportKeys m) { MutableTransportKeys m) {
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m); MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
keys.put(keySetId, ks); keys.put(keySetId, ks);
encodeTags(keySetId, contactId, m.getPreviousIncomingKeys()); if (contactId != null) {
encodeTags(keySetId, contactId, m.getCurrentIncomingKeys()); encodeTags(keySetId, contactId, m.getPreviousIncomingKeys());
encodeTags(keySetId, contactId, m.getNextIncomingKeys()); encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
considerReplacingOutgoingKeys(ks); encodeTags(keySetId, contactId, m.getNextIncomingKeys());
considerReplacingOutgoingKeys(ks);
}
} }
// Locking: lock // Locking: lock
@@ -147,9 +150,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) { if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) {
MutableKeySet old = outContexts.get(ks.getContactId()); MutableKeySet old = outContexts.get(ks.getContactId());
if (old == null || if (old == null ||
old.getKeySetId().getInt() < ks.getKeySetId().getInt()) { old.getKeySetId().getInt() < ks.getKeySetId().getInt())
outContexts.put(ks.getContactId(), ks); outContexts.put(ks.getContactId(), ks);
}
} }
} }
@@ -175,8 +177,20 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public KeySetId addContact(Transaction txn, ContactId c, SecretKey master, public void addContact(Transaction txn, ContactId c, SecretKey master,
long timestamp, boolean alice, boolean active) 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
@@ -197,12 +211,31 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
@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 @Override
public void activateKeys(Transaction txn, KeySetId k) throws DbException { public void activateKeys(Transaction txn, KeySetId k) throws DbException {
lock.lock(); lock.lock();
try { try {
MutableKeySet ks = keys.get(k); MutableKeySet ks = keys.get(k);
if (ks == null) throw new IllegalArgumentException(); if (ks == null) throw new IllegalArgumentException();
// Check that the keys have been bound
if (ks.getContactId() == null) throw new IllegalArgumentException();
MutableTransportKeys m = ks.getTransportKeys(); MutableTransportKeys m = ks.getTransportKeys();
m.getCurrentOutgoingKeys().activate(); m.getCurrentOutgoingKeys().activate();
considerReplacingOutgoingKeys(ks); considerReplacingOutgoingKeys(ks);
@@ -212,6 +245,21 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
@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 {
lock.unlock();
}
}
@Override @Override
public void removeContact(ContactId c) { public void removeContact(ContactId c) {
lock.lock(); lock.lock();

View File

@@ -55,8 +55,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).addContact(txn, remote, local, verified, active); oneOf(db).addContact(txn, remote, local, verified, active);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(keyManager).addContact(txn, contactId, master, timestamp, oneOf(keyManager)
alice, active); .addContact(txn, contactId, master, timestamp, alice);
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);

View File

@@ -4,16 +4,13 @@ import org.briarproject.bramble.api.FormatException;
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.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.api.data.BdfReader.DEFAULT_MAX_BUFFER_SIZE;
import static org.briarproject.bramble.data.BdfReaderImpl.DEFAULT_NESTED_LIMIT; import static org.briarproject.bramble.data.BdfReaderImpl.DEFAULT_NESTED_LIMIT;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
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;
@@ -161,32 +158,30 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadString8() throws Exception { public void testReadString8() throws Exception {
String longest = getRandomString(Byte.MAX_VALUE); String longest = StringUtils.getRandomString(Byte.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8")); String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters // "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" + setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex); "41" + "7F" + longHex);
assertEquals("foo", r.readString()); assertEquals("foo", r.readString(Integer.MAX_VALUE));
assertEquals("", r.readString()); assertEquals("", r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString()); assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadString8ChecksMaxLength() throws Exception { public void testReadString8ChecksMaxLength() throws Exception {
int maxBufferSize = 3; // "foo" twice
// "foo", "fooo" setContents("41" + "03" + "666F6F" + "41" + "03" + "666F6F");
setContents("41" + "03" + "666F6F" assertEquals("foo", r.readString(3));
+ "41" + "04" + "666F6F6F", maxBufferSize);
assertEquals("foo", r.readString());
assertTrue(r.hasString()); assertTrue(r.hasString());
r.readString(); r.readString(2);
} }
@Test @Test
public void testSkipString8() throws Exception { public void testSkipString8() throws Exception {
String longest = getRandomString(Byte.MAX_VALUE); String longest = StringUtils.getRandomString(Byte.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8")); String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters // "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" + setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex); "41" + "7F" + longHex);
@@ -198,37 +193,34 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadString16() throws Exception { public void testReadString16() throws Exception {
String shortest = getRandomString(Byte.MAX_VALUE + 1); String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8")); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = getRandomString(Short.MAX_VALUE); String longest = StringUtils.getRandomString(Short.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8")); String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 -1 random letters // 128 random letters and 2^15 -1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex); setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
assertEquals(shortest, r.readString()); assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString()); assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadString16ChecksMaxLength() throws Exception { public void testReadString16ChecksMaxLength() throws Exception {
int maxBufferSize = Byte.MAX_VALUE + 1; String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
String valid = getRandomString(Byte.MAX_VALUE + 1); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String validHex = toHexString(valid.getBytes("UTF-8")); // 128 random letters, twice
String invalidhex = validHex + "20"; setContents("42" + "0080" + shortHex + "42" + "0080" + shortHex);
// 128 random letters, the same plus a space assertEquals(shortest, r.readString(Byte.MAX_VALUE + 1));
setContents("42" + "0080" + validHex
+ "42" + "0081" + invalidhex, maxBufferSize);
assertEquals(valid, r.readString());
assertTrue(r.hasString()); assertTrue(r.hasString());
r.readString(); r.readString(Byte.MAX_VALUE);
} }
@Test @Test
public void testSkipString16() throws Exception { public void testSkipString16() throws Exception {
String shortest = getRandomString(Byte.MAX_VALUE + 1); String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8")); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = getRandomString(Short.MAX_VALUE); String longest = StringUtils.getRandomString(Short.MAX_VALUE);
String longHex = toHexString(longest.getBytes("UTF-8")); String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 - 1 random letters // 128 random letters and 2^15 - 1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex); setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
r.skipString(); r.skipString();
@@ -238,32 +230,30 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadString32() throws Exception { public void testReadString32() throws Exception {
String shortest = getRandomString(Short.MAX_VALUE + 1); String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8")); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters // 2^15 random letters
setContents("44" + "00008000" + shortHex); setContents("44" + "00008000" + shortHex);
assertEquals(shortest, r.readString()); assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadString32ChecksMaxLength() throws Exception { public void testReadString32ChecksMaxLength() throws Exception {
int maxBufferSize = Short.MAX_VALUE + 1; String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
String valid = getRandomString(maxBufferSize); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String validHex = toHexString(valid.getBytes("UTF-8")); // 2^15 random letters, twice
String invalidHex = validHex + "20"; setContents("44" + "00008000" + shortHex +
// 2^15 random letters, the same plus a space "44" + "00008000" + shortHex);
setContents("44" + "00008000" + validHex + assertEquals(shortest, r.readString(Short.MAX_VALUE + 1));
"44" + "00008001" + invalidHex, maxBufferSize);
assertEquals(valid, r.readString());
assertTrue(r.hasString()); assertTrue(r.hasString());
r.readString(); r.readString(Short.MAX_VALUE);
} }
@Test @Test
public void testSkipString32() throws Exception { public void testSkipString32() throws Exception {
String shortest = getRandomString(Short.MAX_VALUE + 1); String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = toHexString(shortest.getBytes("UTF-8")); String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice // 2^15 random letters, twice
setContents("44" + "00008000" + shortHex + setContents("44" + "00008000" + shortHex +
"44" + "00008000" + shortHex); "44" + "00008000" + shortHex);
@@ -275,43 +265,41 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadUtf8String() throws Exception { public void testReadUtf8String() throws Exception {
String unicode = "\uFDD0\uFDD1\uFDD2\uFDD3"; String unicode = "\uFDD0\uFDD1\uFDD2\uFDD3";
String hex = toHexString(unicode.getBytes("UTF-8")); String hex = StringUtils.toHexString(unicode.getBytes("UTF-8"));
// STRING_8 tag, "foo", the empty string, and the test string // STRING_8 tag, "foo", the empty string, and the test string
setContents("41" + "03" + "666F6F" + "41" + "00" + "41" + "0C" + hex); setContents("41" + "03" + "666F6F" + "41" + "00" + "41" + "0C" + hex);
assertEquals("foo", r.readString()); assertEquals("foo", r.readString(Integer.MAX_VALUE));
assertEquals("", r.readString()); assertEquals("", r.readString(Integer.MAX_VALUE));
assertEquals(unicode, r.readString()); assertEquals(unicode, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test @Test
public void testReadRaw8() throws Exception { public void testReadRaw8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE]; byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = toHexString(longest); String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes // {1, 2, 3}, {}, and 127 zero bytes
setContents("51" + "03" + "010203" + "51" + "00" + setContents("51" + "03" + "010203" + "51" + "00" +
"51" + "7F" + longHex); "51" + "7F" + longHex);
assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw()); assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(new byte[0], r.readRaw()); assertArrayEquals(new byte[0], r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readRaw()); assertArrayEquals(longest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadRaw8ChecksMaxLength() throws Exception { public void testReadRaw8ChecksMaxLength() throws Exception {
int maxBufferSize = 3; // {1, 2, 3} twice
// {1, 2, 3}, {1, 2, 3, 4} setContents("51" + "03" + "010203" + "51" + "03" + "010203");
setContents("51" + "03" + "010203" + "51" + "04" + "01020304", assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw(3));
maxBufferSize);
assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw());
assertTrue(r.hasRaw()); assertTrue(r.hasRaw());
r.readRaw(); r.readRaw(2);
} }
@Test @Test
public void testSkipRaw8() throws Exception { public void testSkipRaw8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE]; byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = toHexString(longest); String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes // {1, 2, 3}, {}, and 127 zero bytes
setContents("51" + "03" + "010203" + "51" + "00" + setContents("51" + "03" + "010203" + "51" + "00" +
"51" + "7F" + longHex); "51" + "7F" + longHex);
@@ -324,36 +312,33 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadRaw16() throws Exception { public void testReadRaw16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1]; byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = toHexString(shortest); String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE]; byte[] longest = new byte[Short.MAX_VALUE];
String longHex = toHexString(longest); String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes // 128 zero bytes and 2^15 - 1 zero bytes
setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex); setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
assertArrayEquals(shortest, r.readRaw()); assertArrayEquals(shortest, r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readRaw()); assertArrayEquals(longest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadRaw16ChecksMaxLength() throws Exception { public void testReadRaw16ChecksMaxLength() throws Exception {
int maxBufferSize = Byte.MAX_VALUE + 1; byte[] shortest = new byte[Byte.MAX_VALUE + 1];
byte[] valid = new byte[maxBufferSize]; String shortHex = StringUtils.toHexString(shortest);
String validHex = toHexString(valid); // 128 zero bytes, twice
String invalidHex = validHex + "00"; setContents("52" + "0080" + shortHex + "52" + "0080" + shortHex);
// 128 zero bytes, 129 zero bytes assertArrayEquals(shortest, r.readRaw(Byte.MAX_VALUE + 1));
setContents("52" + "0080" + validHex
+ "52" + "0081" + invalidHex, maxBufferSize);
assertArrayEquals(valid, r.readRaw());
assertTrue(r.hasRaw()); assertTrue(r.hasRaw());
r.readRaw(); r.readRaw(Byte.MAX_VALUE);
} }
@Test @Test
public void testSkipRaw16() throws Exception { public void testSkipRaw16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1]; byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = toHexString(shortest); String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE]; byte[] longest = new byte[Short.MAX_VALUE];
String longHex = toHexString(longest); String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes // 128 zero bytes and 2^15 - 1 zero bytes
setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex); setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
r.skipRaw(); r.skipRaw();
@@ -364,31 +349,29 @@ public class BdfReaderImplTest extends BrambleTestCase {
@Test @Test
public void testReadRaw32() throws Exception { public void testReadRaw32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1]; byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = toHexString(shortest); String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes // 2^15 zero bytes
setContents("54" + "00008000" + shortHex); setContents("54" + "00008000" + shortHex);
assertArrayEquals(shortest, r.readRaw()); assertArrayEquals(shortest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof()); assertTrue(r.eof());
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testReadRaw32ChecksMaxLength() throws Exception { public void testReadRaw32ChecksMaxLength() throws Exception {
int maxBufferSize = Short.MAX_VALUE + 1; byte[] shortest = new byte[Short.MAX_VALUE + 1];
byte[] valid = new byte[maxBufferSize]; String shortHex = StringUtils.toHexString(shortest);
String validHex = toHexString(valid); // 2^15 zero bytes, twice
String invalidHex = validHex + "00"; setContents("54" + "00008000" + shortHex +
// 2^15 zero bytes, 2^15 + 1 zero bytes "54" + "00008000" + shortHex);
setContents("54" + "00008000" + validHex + assertArrayEquals(shortest, r.readRaw(Short.MAX_VALUE + 1));
"54" + "00008001" + invalidHex, maxBufferSize);
assertArrayEquals(valid, r.readRaw());
assertTrue(r.hasRaw()); assertTrue(r.hasRaw());
r.readRaw(); r.readRaw(Short.MAX_VALUE);
} }
@Test @Test
public void testSkipRaw32() throws Exception { public void testSkipRaw32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1]; byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = toHexString(shortest); String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice // 2^15 zero bytes, twice
setContents("54" + "00008000" + shortHex + setContents("54" + "00008000" + shortHex +
"54" + "00008000" + shortHex); "54" + "00008000" + shortHex);
@@ -410,30 +393,6 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertEquals(NULL_VALUE, list.get(2)); assertEquals(NULL_VALUE, list.get(2));
} }
@Test(expected = FormatException.class)
public void testReadListChecksMaxLengthForString() throws Exception {
// A list containing "foo", a list containing "fooo"
setContents("60" + "41" + "03" + "666F6F" + "80"
+ "60" + "41" + "04" + "666F6F6F" + "80", 3);
BdfList list = r.readList();
assertEquals(1, list.size());
assertEquals("foo", list.get(0));
assertTrue(r.hasList());
r.readList();
}
@Test(expected = FormatException.class)
public void testReadListChecksMaxLengthForRaw() throws Exception {
// A list containing {1, 2, 3}, a list containing {1, 2, 3, 4}
setContents("60" + "51" + "03" + "010203" + "80"
+ "60" + "51" + "04" + "01020304" + "80", 3);
BdfList list = r.readList();
assertEquals(1, list.size());
assertArrayEquals(new byte[] {1, 2, 3}, (byte[]) list.get(0));
assertTrue(r.hasList());
r.readList();
}
@Test @Test
public void testReadListManually() throws Exception { public void testReadListManually() throws Exception {
// A list containing 1, "foo", and null // A list containing 1, "foo", and null
@@ -444,7 +403,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertFalse(r.hasListEnd()); assertFalse(r.hasListEnd());
assertEquals(1, r.readLong()); assertEquals(1, r.readLong());
assertFalse(r.hasListEnd()); assertFalse(r.hasListEnd());
assertEquals("foo", r.readString()); assertEquals("foo", r.readString(1000));
assertFalse(r.hasListEnd()); assertFalse(r.hasListEnd());
assertTrue(r.hasNull()); assertTrue(r.hasNull());
r.readNull(); r.readNull();
@@ -476,47 +435,6 @@ public class BdfReaderImplTest extends BrambleTestCase {
assertEquals(NULL_VALUE, dictionary.get("bar")); assertEquals(NULL_VALUE, dictionary.get("bar"));
} }
@Test(expected = FormatException.class)
public void testReadDictionaryChecksMaxLengthForKey() throws Exception {
// A dictionary containing "foo" -> null, a dictionary containing
// "fooo" -> null
setContents("70" + "41" + "03" + "666F6F" + "00" + "80"
+ "70" + "41" + "04" + "666F6F6F" + "00" + "80", 3);
BdfDictionary dictionary = r.readDictionary();
assertEquals(1, dictionary.size());
assertEquals(NULL_VALUE, dictionary.get("foo"));
assertTrue(r.hasDictionary());
r.readDictionary();
}
@Test(expected = FormatException.class)
public void testReadDictionaryChecksMaxLengthForString() throws Exception {
// A dictionary containing "foo" -> "bar", a dictionary containing
// "foo" -> "baar"
String foo = "41" + "03" + "666F6F";
setContents("70" + foo + "41" + "03" + "626172" + "80"
+ "70" + foo + "41" + "04" + "62616172" + "80", 3);
BdfDictionary dictionary = r.readDictionary();
assertEquals(1, dictionary.size());
assertEquals("bar", dictionary.get("foo"));
assertTrue(r.hasDictionary());
r.readDictionary();
}
@Test(expected = FormatException.class)
public void testReadDictionaryChecksMaxLengthForRaw() throws Exception {
// A dictionary containing "foo" -> {1, 2, 3}, a dictionary containing
// "foo" -> {1, 2, 3, 4}
String foo = "41" + "03" + "666F6F";
setContents("70" + foo + "51" + "03" + "010203" + "80"
+ "70" + foo + "51" + "04" + "01020304" + "80", 3);
BdfDictionary dictionary = r.readDictionary();
assertEquals(1, dictionary.size());
assertArrayEquals(new byte[] {1, 2, 3}, (byte[]) dictionary.get("foo"));
assertTrue(r.hasDictionary());
r.readDictionary();
}
@Test @Test
public void testReadDictionaryManually() throws Exception { public void testReadDictionaryManually() throws Exception {
// A dictionary containing "foo" -> 123 and "bar" -> null // A dictionary containing "foo" -> 123 and "bar" -> null
@@ -524,11 +442,11 @@ public class BdfReaderImplTest extends BrambleTestCase {
"41" + "03" + "626172" + "00" + "80"); "41" + "03" + "626172" + "00" + "80");
r.readDictionaryStart(); r.readDictionaryStart();
assertFalse(r.hasDictionaryEnd()); assertFalse(r.hasDictionaryEnd());
assertEquals("foo", r.readString()); assertEquals("foo", r.readString(1000));
assertFalse(r.hasDictionaryEnd()); assertFalse(r.hasDictionaryEnd());
assertEquals(123, r.readLong()); assertEquals(123, r.readLong());
assertFalse(r.hasDictionaryEnd()); assertFalse(r.hasDictionaryEnd());
assertEquals("bar", r.readString()); assertEquals("bar", r.readString(1000));
assertFalse(r.hasDictionaryEnd()); assertFalse(r.hasDictionaryEnd());
assertTrue(r.hasNull()); assertTrue(r.hasNull());
r.readNull(); r.readNull();
@@ -619,11 +537,8 @@ public class BdfReaderImplTest extends BrambleTestCase {
} }
private void setContents(String hex) { private void setContents(String hex) {
setContents(hex, DEFAULT_MAX_BUFFER_SIZE); ByteArrayInputStream in = new ByteArrayInputStream(
} StringUtils.fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT);
private void setContents(String hex, int maxBufferSize) {
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
} }
} }

View File

@@ -289,11 +289,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(16).of(database).startTransaction(); exactly(17).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(16).of(database).containsContact(txn, contactId); exactly(17).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(16).of(database).abortTransaction(txn); exactly(17).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
@@ -308,6 +308,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
transaction = db.startTransaction(false);
try {
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
fail();
} catch (NoSuchContactException expected) {
// Expected
} finally {
db.endTransaction(transaction);
}
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.generateAck(transaction, contactId, 123); db.generateAck(transaction, contactId, 123);
@@ -763,11 +773,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(5).of(database).startTransaction(); exactly(6).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(5).of(database).containsTransport(txn, transportId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
exactly(6).of(database).containsTransport(txn, transportId);
will(returnValue(false)); will(returnValue(false));
exactly(5).of(database).abortTransaction(txn); exactly(6).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
@@ -782,6 +794,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
transaction = db.startTransaction(false);
try {
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
fail();
} catch (NoSuchTransportException expected) {
// Expected
} finally {
db.endTransaction(transaction);
}
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.getTransportKeys(transaction, transportId); db.getTransportKeys(transaction, transportId);

View File

@@ -94,7 +94,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final TransportId transportId; private final TransportId transportId;
private final ContactId contactId; private final ContactId contactId;
private final KeySetId keySetId, keySetId1; private final KeySetId keySetId, keySetId1;
private final Random random = new Random();
JdbcDatabaseTest() throws Exception { JdbcDatabaseTest() throws Exception {
clientId = getClientId(); clientId = getClientId();
@@ -671,9 +670,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
long rotationPeriod = 123, rotationPeriod1 = 234; long rotationPeriod = 123, rotationPeriod1 = 234;
boolean active = random.nextBoolean(); TransportKeys keys = createTransportKeys(rotationPeriod);
TransportKeys keys = createTransportKeys(rotationPeriod, active); TransportKeys keys1 = createTransportKeys(rotationPeriod1);
TransportKeys keys1 = createTransportKeys(rotationPeriod1, active);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -684,7 +682,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// 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, active)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
@@ -703,9 +701,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
// Rotate the transport keys // Rotate the transport keys
TransportKeys rotated = createTransportKeys(rotationPeriod + 1, active); TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
TransportKeys rotated1 = TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
createTransportKeys(rotationPeriod1 + 1, active);
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated)); db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1)); db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1));
@@ -730,6 +727,95 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); db.close();
} }
@Test
public void testUnboundTransportKeys() throws Exception {
long rotationPeriod = 123, rotationPeriod1 = 234;
TransportKeys keys = createTransportKeys(rotationPeriod);
TransportKeys keys1 = createTransportKeys(rotationPeriod1);
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());
}
}
// Rotate the transport keys
TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
db.updateTransportKeys(txn, new KeySet(keySetId1, null, rotated1));
// Retrieve the transport keys again
allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size());
for (KeySet ks : allKeys) {
if (ks.getKeySetId().equals(keySetId)) {
assertEquals(contactId, ks.getContactId());
assertKeysEquals(rotated, ks.getTransportKeys());
} else {
assertEquals(keySetId1, ks.getKeySetId());
assertNull(ks.getContactId());
assertKeysEquals(rotated1, 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(rotated, 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, private void assertKeysEquals(TransportKeys expected,
TransportKeys actual) { TransportKeys actual) {
assertEquals(expected.getTransportId(), actual.getTransportId()); assertEquals(expected.getTransportId(), actual.getTransportId());
@@ -767,7 +853,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testIncrementStreamCounter() throws Exception { public void testIncrementStreamCounter() throws Exception {
long rotationPeriod = 123; long rotationPeriod = 123;
TransportKeys keys = createTransportKeys(rotationPeriod, true); TransportKeys keys = createTransportKeys(rotationPeriod);
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -807,9 +893,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testSetReorderingWindow() throws Exception { public void testSetReorderingWindow() throws Exception {
boolean active = random.nextBoolean();
long rotationPeriod = 123; long rotationPeriod = 123;
TransportKeys keys = createTransportKeys(rotationPeriod, active); TransportKeys keys = createTransportKeys(rotationPeriod);
long base = keys.getCurrentIncomingKeys().getWindowBase(); long base = keys.getCurrentIncomingKeys().getWindowBase();
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
@@ -819,12 +904,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add the contact, transport and transport keys // Add the contact, transport and 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, active)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
// Update the reordering window and retrieve the transport keys // Update the reordering window and retrieve the transport keys
random.nextBytes(bitmap); new Random().nextBytes(bitmap);
db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod, db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod,
base + 1, bitmap); base + 1, bitmap);
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId); Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
@@ -1823,8 +1908,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
return db; return db;
} }
private TransportKeys createTransportKeys(long rotationPeriod, private TransportKeys createTransportKeys(long rotationPeriod) {
boolean active) {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
@@ -1840,7 +1924,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,
rotationPeriod, 456, active); rotationPeriod, 456, true);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }

View File

@@ -7,10 +7,11 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.test.BrambleTestCase;
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.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays; import java.util.Arrays;
@@ -18,27 +19,33 @@ import java.util.Collections;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
public class SimplexOutgoingSessionTest extends BrambleMockTestCase { public class SimplexOutgoingSessionTest extends BrambleTestCase {
private static final int MAX_LATENCY = Integer.MAX_VALUE; private final Mockery context;
private final DatabaseComponent db;
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final MessageId messageId;
private final int maxLatency;
private final SyncRecordWriter recordWriter;
private final DatabaseComponent db = context.mock(DatabaseComponent.class); public SimplexOutgoingSessionTest() {
private final EventBus eventBus = context.mock(EventBus.class); context = new Mockery();
private final StreamWriter streamWriter = context.mock(StreamWriter.class); db = context.mock(DatabaseComponent.class);
private final SyncRecordWriter recordWriter = dbExecutor = new ImmediateExecutor();
context.mock(SyncRecordWriter.class); eventBus = context.mock(EventBus.class);
recordWriter = context.mock(SyncRecordWriter.class);
private final Executor dbExecutor = new ImmediateExecutor(); contactId = new ContactId(234);
private final ContactId contactId = new ContactId(234); messageId = new MessageId(TestUtils.getRandomId());
private final MessageId messageId = new MessageId(getRandomId()); maxLatency = Integer.MAX_VALUE;
}
@Test @Test
public void testNothingToSend() throws Exception { public void testNothingToSend() throws Exception {
SimplexOutgoingSession session = new SimplexOutgoingSession(db, SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter, dbExecutor, eventBus, contactId, maxLatency, recordWriter);
recordWriter);
Transaction noAckTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false);
Transaction noMsgTxn = new Transaction(null, false); Transaction noMsgTxn = new Transaction(null, false);
@@ -56,17 +63,19 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(noMsgTxn)); will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId), oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(MAX_LATENCY)); with(any(int.class)), with(maxLatency));
will(returnValue(null)); will(returnValue(null));
oneOf(db).commitTransaction(noMsgTxn); oneOf(db).commitTransaction(noMsgTxn);
oneOf(db).endTransaction(noMsgTxn); oneOf(db).endTransaction(noMsgTxn);
// Send the end of stream marker // Flush the output stream
oneOf(streamWriter).sendEndOfStream(); oneOf(recordWriter).flush();
// Remove listener // Remove listener
oneOf(eventBus).removeListener(session); oneOf(eventBus).removeListener(session);
}}); }});
session.run(); session.run();
context.assertIsSatisfied();
} }
@Test @Test
@@ -74,8 +83,7 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
Ack ack = new Ack(Collections.singletonList(messageId)); Ack ack = new Ack(Collections.singletonList(messageId));
byte[] raw = new byte[1234]; byte[] raw = new byte[1234];
SimplexOutgoingSession session = new SimplexOutgoingSession(db, SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter, dbExecutor, eventBus, contactId, maxLatency, recordWriter);
recordWriter);
Transaction ackTxn = new Transaction(null, false); Transaction ackTxn = new Transaction(null, false);
Transaction noAckTxn = new Transaction(null, false); Transaction noAckTxn = new Transaction(null, false);
Transaction msgTxn = new Transaction(null, false); Transaction msgTxn = new Transaction(null, false);
@@ -96,7 +104,7 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(msgTxn)); will(returnValue(msgTxn));
oneOf(db).generateBatch(with(msgTxn), with(contactId), oneOf(db).generateBatch(with(msgTxn), with(contactId),
with(any(int.class)), with(MAX_LATENCY)); with(any(int.class)), with(maxLatency));
will(returnValue(Arrays.asList(raw))); will(returnValue(Arrays.asList(raw)));
oneOf(db).commitTransaction(msgTxn); oneOf(db).commitTransaction(msgTxn);
oneOf(db).endTransaction(msgTxn); oneOf(db).endTransaction(msgTxn);
@@ -112,16 +120,18 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(noMsgTxn)); will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId), oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(MAX_LATENCY)); with(any(int.class)), with(maxLatency));
will(returnValue(null)); will(returnValue(null));
oneOf(db).commitTransaction(noMsgTxn); oneOf(db).commitTransaction(noMsgTxn);
oneOf(db).endTransaction(noMsgTxn); oneOf(db).endTransaction(noMsgTxn);
// Send the end of stream marker // Flush the output stream
oneOf(streamWriter).sendEndOfStream(); oneOf(recordWriter).flush();
// Remove listener // Remove listener
oneOf(eventBus).removeListener(session); oneOf(eventBus).removeListener(session);
}}); }});
session.run(); session.run();
context.assertIsSatisfied();
} }
} }

View File

@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory; import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
@@ -28,6 +27,7 @@ import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -102,18 +102,18 @@ public class SyncIntegrationTest extends BrambleTestCase {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamContext ctx = new StreamContext(contactId, transportId, tagKey, StreamContext ctx = new StreamContext(contactId, transportId, tagKey,
headerKey, streamNumber); headerKey, streamNumber);
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out, OutputStream streamWriter = streamWriterFactory.createStreamWriter(out,
ctx); ctx);
SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter( SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(
streamWriter.getOutputStream()); streamWriter);
recordWriter.writeAck(new Ack(messageIds)); recordWriter.writeAck(new Ack(messageIds));
recordWriter.writeMessage(message.getRaw()); recordWriter.writeMessage(message.getRaw());
recordWriter.writeMessage(message1.getRaw()); recordWriter.writeMessage(message1.getRaw());
recordWriter.writeOffer(new Offer(messageIds)); recordWriter.writeOffer(new Offer(messageIds));
recordWriter.writeRequest(new Request(messageIds)); recordWriter.writeRequest(new Request(messageIds));
recordWriter.flush();
streamWriter.sendEndOfStream();
return out.toByteArray(); return out.toByteArray();
} }

View File

@@ -9,31 +9,25 @@ import java.io.File;
@NotNullByDefault @NotNullByDefault
public class TestDatabaseConfig implements DatabaseConfig { public class TestDatabaseConfig implements DatabaseConfig {
private final File dbDir, keyDir; private final File dir;
private final long maxSize; private final long maxSize;
private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]); private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]);
public TestDatabaseConfig(File testDir, long maxSize) { public TestDatabaseConfig(File dir, long maxSize) {
dbDir = new File(testDir, "db"); this.dir = dir;
keyDir = new File(testDir, "key");
this.maxSize = maxSize; this.maxSize = maxSize;
} }
@Override @Override
public boolean databaseExists() { public boolean databaseExists() {
if (!dbDir.isDirectory()) return false; if (!dir.isDirectory()) return false;
File[] files = dbDir.listFiles(); File[] files = dir.listFiles();
return files != null && files.length > 0; return files != null && files.length > 0;
} }
@Override @Override
public File getDatabaseDirectory() { public File getDatabaseDirectory() {
return dbDir; return dir;
}
@Override
public File getDatabaseKeyDirectory() {
return keyDir;
} }
@Override @Override

View File

@@ -22,7 +22,6 @@ import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import java.util.Random; import java.util.Random;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -55,7 +54,6 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
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 Random random = new Random();
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor, private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
pluginConfig, transportKeyManagerFactory); pluginConfig, transportKeyManagerFactory);
@@ -104,18 +102,30 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
public void testAddContact() throws Exception { public void testAddContact() throws Exception {
SecretKey secretKey = getSecretKey(); SecretKey secretKey = getSecretKey();
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
boolean alice = random.nextBoolean(); boolean alice = new Random().nextBoolean();
boolean active = random.nextBoolean();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportKeyManager).addContact(txn, contactId, secretKey, oneOf(transportKeyManager).addContact(txn, contactId, secretKey,
timestamp, alice, active); timestamp, alice);
}});
keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
}
@Test
public void testAddUnboundKeys() throws Exception {
SecretKey secretKey = getSecretKey();
long timestamp = System.currentTimeMillis();
boolean alice = new Random().nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addUnboundKeys(txn, secretKey,
timestamp, alice);
will(returnValue(keySetId)); will(returnValue(keySetId));
}}); }});
Map<TransportId, KeySetId> ids = keyManager.addContact(txn, contactId, assertEquals(singletonMap(transportId, keySetId),
secretKey, timestamp, alice, active); keyManager.addUnboundKeys(txn, secretKey, timestamp, alice));
assertEquals(singletonMap(transportId, keySetId), ids);
} }
@Test @Test

View File

@@ -61,6 +61,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final ContactId contactId1 = new ContactId(234); private final ContactId contactId1 = new ContactId(234);
private final KeySetId keySetId = new KeySetId(345); private final KeySetId keySetId = new KeySetId(345);
private final KeySetId keySetId1 = new KeySetId(456); 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();
@@ -70,11 +71,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
public void testKeysAreRotatedAtStartup() throws Exception { public void testKeysAreRotatedAtStartup() throws Exception {
TransportKeys shouldRotate = createTransportKeys(900, 0, true); TransportKeys shouldRotate = createTransportKeys(900, 0, true);
TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true); TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true);
TransportKeys shouldRotate1 = createTransportKeys(999, 0, false);
Collection<KeySet> loaded = asList( Collection<KeySet> loaded = asList(
new KeySet(keySetId, contactId, shouldRotate), new KeySet(keySetId, contactId, shouldRotate),
new KeySet(keySetId1, contactId1, shouldNotRotate) new KeySet(keySetId1, contactId1, shouldNotRotate),
new KeySet(keySetId2, null, shouldRotate1)
); );
TransportKeys rotated = createTransportKeys(1000, 0, true); 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() {{
@@ -89,6 +93,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
will(returnValue(rotated)); will(returnValue(rotated));
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000); oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
will(returnValue(shouldNotRotate)); will(returnValue(shouldNotRotate));
oneOf(transportCrypto).rotateTransportKeys(shouldRotate1, 1000);
will(returnValue(rotated1));
// Encode the tags (3 sets per contact) // Encode the tags (3 sets per contact)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(6).of(transportCrypto).encodeTag( exactly(6).of(transportCrypto).encodeTag(
@@ -97,8 +103,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Save the keys that were rotated // Save the keys that were rotated
oneOf(db).updateTransportKeys(txn, oneOf(db).updateTransportKeys(txn, asList(
singletonList(new KeySet(keySetId, contactId, rotated))); new KeySet(keySetId, contactId, rotated),
new KeySet(keySetId2, null, rotated1))
);
// Schedule key rotation at the start of the next rotation period // Schedule key rotation at the start of the next rotation period
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with(rotationPeriodLength - 1), with(MILLISECONDS)); with(rotationPeriodLength - 1), with(MILLISECONDS));
@@ -145,11 +153,43 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is 1 ms before the start of rotation period 1000 // The timestamp is 1 ms before the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000 - 1; long timestamp = rotationPeriodLength * 1000 - 1;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
masterKey, timestamp, alice, true)); alice);
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); 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
public void testOutgoingStreamContextIsNullIfContactIsNotFound() public void testOutgoingStreamContextIsNullIfContactIsNotFound()
throws Exception { throws Exception {
@@ -171,15 +211,15 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
MAX_32_BIT_UNSIGNED + 1, true); MAX_32_BIT_UNSIGNED + 1, true);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, true, transportKeys, txn); expectAddContactNoRotation(alice, transportKeys, txn);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
masterKey, timestamp, alice, true)); alice);
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
} }
@@ -192,7 +232,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
MAX_32_BIT_UNSIGNED, true); MAX_32_BIT_UNSIGNED, true);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, true, transportKeys, txn); expectAddContactNoRotation(alice, transportKeys, txn);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Increment the stream counter // Increment the stream counter
@@ -204,8 +244,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
masterKey, timestamp, alice, true)); alice);
// The first request should return a stream context // The first request should return a stream context
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
StreamContext ctx = transportKeyManager.getStreamContext(txn, StreamContext ctx = transportKeyManager.getStreamContext(txn,
@@ -225,21 +265,19 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
public void testIncomingStreamContextIsNullIfTagIsNotFound() public void testIncomingStreamContextIsNullIfTagIsNotFound()
throws Exception { throws Exception {
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
boolean active = random.nextBoolean(); TransportKeys transportKeys = createTransportKeys(1000, 0, true);
TransportKeys transportKeys = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, active, transportKeys, txn); expectAddContactNoRotation(alice, transportKeys, txn);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
masterKey, timestamp, alice, active)); alice);
assertEquals(active, assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
transportKeyManager.canSendOutgoingStreams(contactId));
// The tag should not be recognised // The tag should not be recognised
assertNull(transportKeyManager.getStreamContext(txn, assertNull(transportKeyManager.getStreamContext(txn,
new byte[TAG_LENGTH])); new byte[TAG_LENGTH]));
@@ -289,8 +327,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
masterKey, timestamp, alice, true)); alice);
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); 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());
@@ -377,14 +415,23 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testActivatingKeys() throws Exception { public void testBindingAndActivatingKeys() throws Exception {
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(1000, 0, false); TransportKeys transportKeys = createTransportKeys(1000, 0, false);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
expectAddContactNoRotation(alice, false, transportKeys, txn); expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
context.checking(new Expectations() {{ 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 // Activate the keys
oneOf(db).setTransportKeysActive(txn, transportId, keySetId); oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
// Increment the stream counter // Increment the stream counter
@@ -396,8 +443,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
masterKey, timestamp, alice, false)); 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 // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -423,26 +474,18 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
// Keep a copy of the tags // Keep a copy of the tags
List<byte[]> tags = new ArrayList<>(); List<byte[]> tags = new ArrayList<>();
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, // When the keys are bound, encode the tags (3 sets)
1000, alice, false);
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++) { for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag( exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey), with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i)); with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags)); will(new EncodeTagAction(tags));
} }
// Rotate the transport keys (the keys are unaffected) // Save the key binding
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
will(returnValue(transportKeys));
// Save the keys
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),
@@ -462,8 +505,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
masterKey, timestamp, alice, false)); masterKey, timestamp, alice));
transportKeyManager.bindKeys(txn, contactId, keySetId);
// The keys are inactive so no stream context should be returned // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -488,11 +532,36 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertEquals(0L, ctx.getStreamNumber()); assertEquals(0L, ctx.getStreamNumber());
} }
private void expectAddContactNoRotation(boolean alice, boolean active, @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 { TransportKeys transportKeys, Transaction txn) throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
1000, alice, active); 1000, alice, true);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000) // Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
@@ -513,6 +582,24 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
}}); }});
} }
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, boolean active) {
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey, IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,

View File

@@ -33,6 +33,10 @@ dependencies {
implementation 'com.jpardogo.materialtabstrip:library:1.1.0' implementation 'com.jpardogo.materialtabstrip:library:1.1.0'
implementation 'com.github.bumptech.glide:glide:3.8.0' implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0' implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0'
implementation'fr.bmartel:jspeedtest:1.32.0'
implementation 'com.jjoe64:graphview:4.2.2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2' annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
@@ -218,16 +222,16 @@ dependencyVerification {
] ]
} }
def getStdout = { command, defaultValue -> def getGitHash = { ->
def stdout = new ByteArrayOutputStream() def stdout = new ByteArrayOutputStream()
try { try {
exec { exec {
commandLine = command commandLine 'git', 'rev-parse', '--short=7', 'HEAD'
standardOutput = stdout standardOutput = stdout
} }
return stdout.toString().trim() return stdout.toString().trim()
} catch (Exception ignored) { } catch (Exception ignored) {
return defaultValue return "No commit hash"
} }
} }
@@ -238,15 +242,12 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 10005 versionCode 10001
versionName "1.0.5" versionName "1.0.1"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
resValue "string", "app_package", "org.briarproject.briar.android" resValue "string", "app_package", "org.briarproject.briar.android"
resValue "string", "app_name", "Briar" resValue "string", "app_name", "Briar"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash", "\"${getGitHash()}\""
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
buildConfigField "Long", "BuildTimestamp",
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], 0)}000L"
} }
buildTypes { buildTypes {
@@ -256,13 +257,11 @@ android {
resValue "string", "app_name", "Briar Debug" resValue "string", "app_name", "Briar Debug"
shrinkResources false shrinkResources false
minifyEnabled true minifyEnabled true
crunchPngs false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
} }
release { release {
shrinkResources true shrinkResources true
minifyEnabled true minifyEnabled true
crunchPngs false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
} }
} }
@@ -278,6 +277,10 @@ android {
} }
} }
aaptOptions {
cruncherEnabled = false
}
lintOptions { lintOptions {
warning 'MissingTranslation' warning 'MissingTranslation'
warning 'ImpliedQuantity' warning 'ImpliedQuantity'

View File

@@ -4,16 +4,18 @@
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.bluetooth"/> <uses-feature android:name="android.hardware.bluetooth"/>
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application <application
@@ -68,8 +70,8 @@
<activity <activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity" android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
android:theme="@style/BriarTheme.NoActionBar" android:label="@string/app_name"
android:label="@string/app_name"> android:theme="@style/BriarTheme.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@@ -83,15 +85,15 @@
<activity <activity
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity" android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar" android:launchMode="singleTop"
android:launchMode="singleTop"> android:theme="@style/BriarTheme.NoActionBar">
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.contact.ConversationActivity" android:name="org.briarproject.briar.android.contact.ConversationActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/BriarTheme.NoActionBar"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity" android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="stateHidden|adjustResize"> android:windowSoftInputMode="stateHidden|adjustResize">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@@ -131,7 +133,7 @@
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/> android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity" android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
android:label="@string/groups_member_list" android:label="@string/groups_member_list"
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity" android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
@@ -303,8 +305,8 @@
<activity <activity
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity" android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
android:label="@string/add_contact_title" android:label="@string/add_contact_title"
android:theme="@style/BriarTheme.NoActionBar" android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"> android:theme="@style/BriarTheme.NoActionBar">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/> android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
@@ -361,6 +363,16 @@
/> />
</activity> </activity>
<activity
android:name="org.briarproject.briar.android.test.PollingTestActivity"
android:label="Test polling"
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"

View File

@@ -1,101 +0,0 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
@NotNullByDefault
class AndroidDatabaseConfig implements DatabaseConfig {
private static final Logger LOG =
Logger.getLogger(AndroidDatabaseConfig.class.getName());
private final File dbDir, keyDir;
@Nullable
private volatile SecretKey key = null;
@Nullable
private volatile String nickname = null;
AndroidDatabaseConfig(File dbDir, File keyDir) {
this.dbDir = dbDir;
this.keyDir = keyDir;
}
@Override
public boolean databaseExists() {
// FIXME should not run on UiThread #620
if (!dbDir.isDirectory()) {
if (LOG.isLoggable(INFO))
LOG.info(dbDir.getAbsolutePath() + " is not a directory");
return false;
}
File[] files = dbDir.listFiles();
if (LOG.isLoggable(INFO)) {
if (files == null) {
LOG.info("Could not list files in " + dbDir.getAbsolutePath());
} else {
LOG.info("Files in " + dbDir.getAbsolutePath() + ":");
for (File f : files) LOG.info(f.getName());
}
LOG.info("Database exists: " + (files != null && files.length > 0));
}
return files != null && files.length > 0;
}
@Override
public File getDatabaseDirectory() {
if (LOG.isLoggable(INFO))
LOG.info("Database directory: " + dbDir.getAbsolutePath());
return dbDir;
}
@Override
public File getDatabaseKeyDirectory() {
if (LOG.isLoggable(INFO))
LOG.info("Database key directory: " + keyDir.getAbsolutePath());
return keyDir;
}
@Override
public void setEncryptionKey(SecretKey key) {
LOG.info("Setting database key");
this.key = key;
}
@Override
public void setLocalAuthorName(String nickname) {
LOG.info("Setting local author name");
this.nickname = nickname;
}
@Override
@Nullable
public String getLocalAuthorName() {
String nickname = this.nickname;
if (LOG.isLoggable(INFO))
LOG.info("Local author name has been set: " + (nickname != null));
return nickname;
}
@Override
@Nullable
public SecretKey getEncryptionKey() {
SecretKey key = this.key;
if (LOG.isLoggable(INFO))
LOG.info("Database key has been set: " + (key != null));
return key;
}
@Override
public long getMaxSize() {
return Long.MAX_VALUE;
}
}

View File

@@ -2,10 +2,10 @@ package org.briarproject.briar.android;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.StrictMode;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.ui.UiCallback; import org.briarproject.bramble.api.ui.UiCallback;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -24,6 +23,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
import java.io.File; import java.io.File;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -84,22 +84,60 @@ public class AppModule {
@Provides @Provides
@Singleton @Singleton
DatabaseConfig provideDatabaseConfig(Application app) { DatabaseConfig provideDatabaseConfig(Application app) {
//FIXME: StrictMode File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskReads();
StrictMode.allowThreadDiskWrites();
File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE);
StrictMode.setThreadPolicy(tp);
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
DatabaseConfig databaseConfig = DatabaseConfig databaseConfig = new DatabaseConfig() {
new AndroidDatabaseConfig(dbDir, keyDir);
private volatile SecretKey key;
private volatile String nickname;
@Override
public boolean databaseExists() {
// FIXME should not run on UiThread #620
if (!dir.isDirectory()) return false;
File[] files = dir.listFiles();
return files != null && files.length > 0;
}
@Override
public File getDatabaseDirectory() {
return dir;
}
@Override
public void setEncryptionKey(SecretKey key) {
this.key = key;
}
@Override
public void setLocalAuthorName(String nickname) {
this.nickname = nickname;
}
@Override
@Nullable
public String getLocalAuthorName() {
return nickname;
}
@Override
@Nullable
public SecretKey getEncryptionKey() {
return key;
}
@Override
public long getMaxSize() {
return Long.MAX_VALUE;
}
};
return databaseConfig; return databaseConfig;
} }
@Provides @Provides
@Singleton @Singleton
DevConfig provideDevConfig(Application app, CryptoComponent crypto) { DevConfig provideDevConfig(CryptoComponent crypto) {
@NotNullByDefault @NotNullByDefault
DevConfig devConfig = new DevConfig() { DevConfig devConfig = new DevConfig() {
@@ -117,11 +155,6 @@ public class AppModule {
public String getDevOnionAddress() { public String getDevOnionAddress() {
return DEV_ONION_ADDRESS; return DEV_ONION_ADDRESS;
} }
@Override
public File getReportDir() {
return AndroidUtils.getReportDir(app.getApplicationContext());
}
}; };
return devConfig; return devConfig;
} }
@@ -171,5 +204,4 @@ public class AppModule {
lifecycleManager.registerService(dozeWatchdog); lifecycleManager.registerService(dozeWatchdog);
return dozeWatchdog; return dozeWatchdog;
} }
} }

View File

@@ -1,15 +1,14 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import java.util.Collection;
import java.util.logging.LogRecord;
/** /**
* This exists so that the Application object will not necessarily be cast * This exists so that the Application object will not necessarily be cast
* directly to the Briar application object. * directly to the Briar application object.
*/ */
public interface BriarApplication { public interface BriarApplication {
Collection<LogRecord> getRecentLogRecords(); // This build expires on 31 December 2018
long EXPIRY_DATE = 1546214400 * 1000L;
AndroidComponent getApplicationComponent(); AndroidComponent getApplicationComponent();
} }

View File

@@ -12,17 +12,12 @@ import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarReportPrimer; import org.briarproject.briar.android.reporting.BriarReportPrimer;
import org.briarproject.briar.android.reporting.BriarReportSenderFactory; import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
import org.briarproject.briar.android.reporting.DevReportActivity; import org.briarproject.briar.android.reporting.DevReportActivity;
import java.util.Collection;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static org.acra.ReportField.ANDROID_VERSION; import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE; import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME; import static org.acra.ReportField.APP_VERSION_NAME;
@@ -33,6 +28,7 @@ import static org.acra.ReportField.CUSTOM_DATA;
import static org.acra.ReportField.DEVICE_FEATURES; import static org.acra.ReportField.DEVICE_FEATURES;
import static org.acra.ReportField.DISPLAY; import static org.acra.ReportField.DISPLAY;
import static org.acra.ReportField.INITIAL_CONFIGURATION; import static org.acra.ReportField.INITIAL_CONFIGURATION;
import static org.acra.ReportField.LOGCAT;
import static org.acra.ReportField.PACKAGE_NAME; import static org.acra.ReportField.PACKAGE_NAME;
import static org.acra.ReportField.PHONE_MODEL; import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.PRODUCT; import static org.acra.ReportField.PRODUCT;
@@ -40,7 +36,7 @@ import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE; import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE; import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE; import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD; import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes( @ReportsCrashes(
@@ -60,7 +56,8 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
STACK_TRACE, STACK_TRACE,
INITIAL_CONFIGURATION, CRASH_CONFIGURATION, INITIAL_CONFIGURATION, CRASH_CONFIGURATION,
DISPLAY, DEVICE_FEATURES, DISPLAY, DEVICE_FEATURES,
USER_APP_START_DATE, USER_CRASH_DATE USER_APP_START_DATE, USER_CRASH_DATE,
LOGCAT
} }
) )
public class BriarApplicationImpl extends Application public class BriarApplicationImpl extends Application
@@ -69,8 +66,6 @@ public class BriarApplicationImpl extends Application
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(BriarApplicationImpl.class.getName()); Logger.getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
@Override @Override
@@ -84,17 +79,7 @@ public class BriarApplicationImpl extends Application
super.onCreate(); super.onCreate();
if (IS_DEBUG_BUILD) enableStrictMode(); if (IS_DEBUG_BUILD) enableStrictMode();
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
Logger rootLogger = Logger.getLogger("");
if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
// Remove default log handlers so system log is not used
for (Handler handler : rootLogger.getHandlers()) {
rootLogger.removeHandler(handler);
}
}
rootLogger.addHandler(logHandler);
rootLogger.setLevel(INFO);
LOG.info("Created"); LOG.info("Created");
applicationComponent = DaggerAndroidComponent.builder() applicationComponent = DaggerAndroidComponent.builder()
@@ -119,11 +104,6 @@ public class BriarApplicationImpl extends Application
StrictMode.setVmPolicy(vmPolicy.build()); StrictMode.setVmPolicy(vmPolicy.build());
} }
@Override
public Collection<LogRecord> getRecentLogRecords() {
return logHandler.getRecentLogRecords();
}
@Override @Override
public AndroidComponent getApplicationComponent() { public AndroidComponent getApplicationComponent() {
return applicationComponent; return applicationComponent;

View File

@@ -22,6 +22,7 @@ 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.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;
@@ -43,7 +44,6 @@ 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;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
@@ -221,52 +221,18 @@ 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");
// Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16
if (SDK_INT < 16) hideUi();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_UI_HIDDEN) {
LOG.info("Trim memory: UI hidden");
} else if (level == TRIM_MEMORY_BACKGROUND) {
LOG.info("Trim memory: added to LRU list");
} else if (level == TRIM_MEMORY_MODERATE) {
LOG.info("Trim memory: near middle of LRU list");
} else if (level == TRIM_MEMORY_COMPLETE) {
LOG.info("Trim memory: near end of LRU list");
} else if (SDK_INT >= 16) {
if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.info("Trim memory: running critically low");
// Clear the UI to save some memory
hideUi();
} else if (LOG.isLoggable(INFO)) {
LOG.info("Trim memory: unknown level " + level);
}
} else if (LOG.isLoggable(INFO)) {
LOG.info("Trim memory: unknown level " + level);
}
}
private void hideUi() {
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);
} }
private void shutdownFromBackground() { private void shutdownFromBackground() {
// Stop the service // Stop the service
stopSelf(); stopSelf();
// Hide the UI // Hide the UI
hideUi(); 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 // Wait for shutdown to complete, then exit
new Thread(() -> { new Thread(() -> {
try { try {
@@ -279,6 +245,25 @@ public class BriarService extends Service {
}).start(); }).start();
} }
// TODO: Remove if low memory shutdowns are not appropriate
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());
});
}
/** /**
* Waits for all services to start before returning. * Waits for all services to start before returning.
*/ */

View File

@@ -14,6 +14,7 @@ import android.content.pm.Signature;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -195,7 +196,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
} }
@Override @Override
public void startService() { public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
androidExecutor.runOnUiThread(() -> { androidExecutor.runOnUiThread(() -> {
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
@@ -211,7 +212,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
} }
@Override @Override
public void stopService() { public void stopService() throws ServiceException {
androidExecutor.runOnUiThread(() -> { androidExecutor.runOnUiThread(() -> {
if (receiver != null) app.unregisterReceiver(receiver); if (receiver != null) app.unregisterReceiver(receiver);
}); });

View File

@@ -2,6 +2,11 @@ package org.briarproject.briar.android;
import org.briarproject.briar.BuildConfig; import org.briarproject.briar.BuildConfig;
import java.util.logging.Level;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.OFF;
public interface TestingConstants { public interface TestingConstants {
/** /**
@@ -15,6 +20,12 @@ public interface TestingConstants {
*/ */
boolean IS_BETA_BUILD = false; boolean IS_BETA_BUILD = false;
/**
* Default log level. Disable logging for final release builds.
*/
@SuppressWarnings("ConstantConditions")
Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
/** /**
* Whether to prevent screenshots from being taken. Setting this to true * Whether to prevent screenshots from being taken. Setting this to true
* prevents Recent Apps from storing screenshots of private information. * prevents Recent Apps from storing screenshots of private information.
@@ -22,12 +33,4 @@ public interface TestingConstants {
* intentionally. * intentionally.
*/ */
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD; boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
/**
* Debug and beta builds expire after 90 days. Final release builds expire
* after 292 million years.
*/
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
Long.MAX_VALUE;
} }

View File

@@ -70,6 +70,7 @@ import org.briarproject.briar.android.sharing.ShareForumFragment;
import org.briarproject.briar.android.sharing.ShareForumMessageFragment; import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
import org.briarproject.briar.android.sharing.SharingModule; import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.splash.SplashScreenActivity; import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.PollingTestActivity;
import org.briarproject.briar.android.test.TestDataActivity; import org.briarproject.briar.android.test.TestDataActivity;
import dagger.Component; import dagger.Component;
@@ -150,6 +151,8 @@ public interface ActivityComponent {
void inject(TestDataActivity activity); void inject(TestDataActivity activity);
void inject(PollingTestActivity activity);
void inject(ChangePasswordActivity activity); void inject(ChangePasswordActivity activity);
void inject(IntroductionActivity activity); void inject(IntroductionActivity activity);

View File

@@ -12,7 +12,7 @@ public interface ConfigController {
@Nullable @Nullable
String getEncryptedDatabaseKey(); String getEncryptedDatabaseKey();
boolean storeEncryptedDatabaseKey(String hex); void storeEncryptedDatabaseKey(String hex);
void deleteAccount(Context ctx); void deleteAccount(Context ctx);

View File

@@ -2,38 +2,20 @@ package org.briarproject.briar.android.controller;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault @NotNullByDefault
public class ConfigControllerImpl implements ConfigController { public class ConfigControllerImpl implements ConfigController {
private static final Logger LOG =
Logger.getLogger(ConfigControllerImpl.class.getName());
private static final String PREF_DB_KEY = "key"; private static final String PREF_DB_KEY = "key";
private static final String DB_KEY_FILENAME = "db.key";
private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
private final SharedPreferences briarPrefs; private final SharedPreferences briarPrefs;
private final File dbKeyFile, dbKeyBackupFile;
protected final DatabaseConfig databaseConfig; protected final DatabaseConfig databaseConfig;
@Inject @Inject
@@ -41,136 +23,39 @@ public class ConfigControllerImpl implements ConfigController {
DatabaseConfig databaseConfig) { DatabaseConfig databaseConfig) {
this.briarPrefs = briarPrefs; this.briarPrefs = briarPrefs;
this.databaseConfig = databaseConfig; this.databaseConfig = databaseConfig;
File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
} }
@Override @Override
@Nullable @Nullable
public String getEncryptedDatabaseKey() { public String getEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences(); return briarPrefs.getString(PREF_DB_KEY, null);
if (key == null) key = getDatabaseKeyFromFile();
else migrateDatabaseKeyToFile(key);
return key;
}
@Nullable
private String getDatabaseKeyFromPreferences() {
String key = briarPrefs.getString(PREF_DB_KEY, null);
if (key == null) LOG.info("No database key in preferences");
else LOG.info("Found database key in preferences");
return key;
}
@Nullable
private String getDatabaseKeyFromFile() {
String key = readDbKeyFromFile(dbKeyFile);
if (key == null) {
LOG.info("No database key in primary file");
key = readDbKeyFromFile(dbKeyBackupFile);
if (key == null) LOG.info("No database key in backup file");
else LOG.warning("Found database key in backup file");
} else {
LOG.info("Found database key in primary file");
}
return key;
}
@Nullable
private String readDbKeyFromFile(File f) {
if (!f.exists()) {
LOG.info("Key file does not exist");
return null;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String key = reader.readLine();
reader.close();
return key;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
private void migrateDatabaseKeyToFile(String key) {
if (storeEncryptedDatabaseKey(key)) {
if (briarPrefs.edit().remove(PREF_DB_KEY).commit())
LOG.info("Database key migrated to file");
else LOG.warning("Database key not removed from preferences");
} else {
LOG.warning("Database key not migrated to file");
}
} }
@Override @Override
public boolean storeEncryptedDatabaseKey(String hex) { public void storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file"); SharedPreferences.Editor editor = briarPrefs.edit();
// Create the directory if necessary editor.putString(PREF_DB_KEY, hex);
if (databaseConfig.getDatabaseKeyDirectory().mkdirs()) editor.apply();
LOG.info("Created database key directory");
// If only the backup file exists, rename it so we don't overwrite it
if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
if (dbKeyBackupFile.renameTo(dbKeyFile))
LOG.info("Renamed old backup");
else LOG.warning("Failed to rename old backup");
}
try {
// Write to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored database key in backup file");
// Delete the old primary file, if it exists
if (dbKeyFile.exists()) {
if (dbKeyFile.delete()) LOG.info("Deleted primary file");
else LOG.warning("Failed to delete primary file");
}
// The backup file becomes the new primary
if (dbKeyBackupFile.renameTo(dbKeyFile)) {
LOG.info("Renamed backup file to primary");
} else {
LOG.warning("Failed to rename backup file to primary");
return false; // Don't overwrite our only copy
}
// Write a second copy to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored second copy of database key in backup file");
return true;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
}
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.flush();
out.close();
} }
@Override @Override
public void deleteAccount(Context ctx) { public void deleteAccount(Context ctx) {
LOG.info("Deleting account"); SharedPreferences.Editor editor = briarPrefs.edit();
SharedPreferences defaultPrefs = editor.clear();
PreferenceManager.getDefaultSharedPreferences(ctx); editor.apply();
AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs); AndroidUtils.deleteAppData(ctx);
AndroidUtils.logDataDirContents(ctx);
} }
@Override @Override
public boolean accountExists() { public boolean accountExists() {
String hex = getEncryptedDatabaseKey(); String hex = getEncryptedDatabaseKey();
boolean exists = hex != null && databaseConfig.databaseExists(); return hex != null && databaseConfig.databaseExists();
if (LOG.isLoggable(INFO)) LOG.info("Account exists: " + exists);
return exists;
} }
@Override @Override
public boolean accountSignedIn() { public boolean accountSignedIn() {
boolean signedIn = databaseConfig.getEncryptionKey() != null; return databaseConfig.getEncryptionKey() != null;
if (LOG.isLoggable(INFO)) LOG.info("Signed in: " + signedIn);
return signedIn;
} }
} }

View File

@@ -1,47 +0,0 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Locale.US;
@ThreadSafe
@NotNullByDefault
public class BriefLogFormatter extends Formatter {
private final Object lock = new Object();
private final DateFormat dateFormat; // Locking: lock
private final Date date; // Locking: lock
public BriefLogFormatter() {
synchronized (lock) {
dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS ", US);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
date = new Date();
}
}
@Override
public String format(LogRecord record) {
String dateString;
synchronized (lock) {
date.setTime(record.getMillis());
dateString = dateFormat.format(date);
}
StringBuilder sb = new StringBuilder(dateString);
sb.append(record.getLevel().getName().charAt(0)).append('/');
String tag = record.getLoggerName();
tag = tag.substring(tag.lastIndexOf('.') + 1);
sb.append(tag).append(": ");
sb.append(record.getMessage());
return sb.toString();
}
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class CachingLogHandler extends Handler {
private static final int MAX_RECENT_RECORDS = 100;
private final Object lock = new Object();
// Locking: lock
private final Queue<LogRecord> recent = new LinkedList<>();
@Override
public void publish(LogRecord record) {
synchronized (lock) {
recent.add(record);
if (recent.size() > MAX_RECENT_RECORDS) recent.poll();
}
}
@Override
public void flush() {
}
@Override
public void close() {
synchronized (lock) {
recent.clear();
}
}
public Collection<LogRecord> getRecentLogRecords() {
synchronized (lock) {
return new ArrayList<>(recent);
}
}
}

View File

@@ -8,21 +8,15 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
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.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class AuthorNameFragment extends SetupFragment { public class AuthorNameFragment extends SetupFragment {
private final static String TAG = AuthorNameFragment.class.getName(); private final static String TAG = AuthorNameFragment.class.getName();
@@ -36,9 +30,8 @@ public class AuthorNameFragment extends SetupFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Nullable ViewGroup container, Bundle savedInstanceState) {
@Nullable Bundle savedInstanceState) {
getActivity().setTitle(getString(R.string.setup_title)); getActivity().setTitle(getString(R.string.setup_title));
View v = inflater.inflate(R.layout.fragment_setup_author_name, View v = inflater.inflate(R.layout.fragment_setup_author_name,
container, false); container, false);
@@ -82,7 +75,6 @@ public class AuthorNameFragment extends SetupFragment {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
setupController.setAuthorName(authorNameInput.getText().toString()); setupController.setAuthorName(authorNameInput.getText().toString());
setupController.showPasswordFragment();
} }
} }

View File

@@ -10,8 +10,7 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.login.PowerView.OnCheckedChangedListener; import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener;
@@ -22,8 +21,7 @@ import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault @NotNullByDefault
@ParametersNotNullByDefault
public class DozeFragment extends SetupFragment public class DozeFragment extends SetupFragment
implements OnCheckedChangedListener { implements OnCheckedChangedListener {

View File

@@ -72,7 +72,8 @@ public class PasswordControllerImpl extends ConfigControllerImpl
} else { } else {
String hex = String hex =
encryptDatabaseKey(new SecretKey(key), newPassword); encryptDatabaseKey(new SecretKey(key), newPassword);
resultHandler.onResult(storeEncryptedDatabaseKey(hex)); storeEncryptedDatabaseKey(hex);
resultHandler.onResult(true);
} }
}); });
} }

View File

@@ -11,21 +11,15 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import javax.annotation.Nullable;
import static android.content.Context.INPUT_METHOD_SERVICE; import static android.content.Context.INPUT_METHOD_SERVICE;
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 org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PasswordFragment extends SetupFragment { public class PasswordFragment extends SetupFragment {
private final static String TAG = PasswordFragment.class.getName(); private final static String TAG = PasswordFragment.class.getName();
@@ -43,9 +37,8 @@ public class PasswordFragment extends SetupFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Nullable ViewGroup container, Bundle savedInstanceState) {
@Nullable Bundle savedInstanceState) {
getActivity().setTitle(getString(R.string.setup_password_intro)); getActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container, View v = inflater.inflate(R.layout.fragment_setup_password, container,
false); false);
@@ -112,17 +105,16 @@ public class PasswordFragment extends SetupFragment {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (!setupController.needToShowDozeFragment()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
String password = passwordEntry.getText().toString();
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setupController.setPassword(passwordEntry.getText().toString()); setupController.setPassword(password);
if (setupController.needToShowDozeFragment()) { setupController.showDozeOrCreateAccount();
setupController.showDozeFragment();
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
} }
} }

View File

@@ -89,9 +89,9 @@ abstract class PowerView extends ConstraintLayout {
public void setChecked(boolean checked) { public void setChecked(boolean checked) {
this.checked = checked; this.checked = checked;
if (checked) { if (checked) {
checkImage.setVisibility(VISIBLE); checkImage.setImageResource(R.drawable.ic_check_white);
} else { } else {
checkImage.setVisibility(INVISIBLE); checkImage.setImageResource(R.drawable.contact_disconnected);
} }
if (onCheckedChangedListener != null) { if (onCheckedChangedListener != null) {
onCheckedChangedListener.onCheckedChanged(); onCheckedChangedListener.onCheckedChanged();

View File

@@ -4,46 +4,30 @@ import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SetupActivity extends BaseActivity public class SetupActivity extends BaseActivity
implements BaseFragmentListener { implements BaseFragmentListener {
private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password";
@Inject @Inject
SetupController setupController; SetupController setupController;
@Nullable
private String authorName, password;
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
// fade-in after splash screen instead of default animation // fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out); overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
if (state == null) { if (state == null) {
if (setupController.accountExists())
throw new AssertionError();
showInitialFragment(AuthorNameFragment.newInstance()); showInitialFragment(AuthorNameFragment.newInstance());
} else {
authorName = state.getString(STATE_KEY_AUTHOR_NAME);
password = state.getString(STATE_KEY_PASSWORD);
} }
} }
@@ -53,46 +37,16 @@ public class SetupActivity extends BaseActivity
setupController.setSetupActivity(this); setupController.setSetupActivity(this);
} }
@Override public void showPasswordFragment() {
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (authorName != null)
state.putString(STATE_KEY_AUTHOR_NAME, authorName);
if (password != null)
state.putString(STATE_KEY_PASSWORD, password);
}
@Nullable
String getAuthorName() {
return authorName;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
}
@Nullable
String getPassword() {
return password;
}
void setPassword(String password) {
this.password = password;
}
void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException();
showNextFragment(PasswordFragment.newInstance()); showNextFragment(PasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { public void showDozeFragment() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
showNextFragment(DozeFragment.newInstance()); showNextFragment(DozeFragment.newInstance());
} }
void showApp() { public void showApp() {
Intent i = new Intent(this, OpenDatabaseActivity.class); Intent i = new Intent(this, OpenDatabaseActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK); i.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(i); startActivity(i);

View File

@@ -1,9 +1,10 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault @NotNullByDefault
public interface SetupController extends PasswordController { public interface SetupController {
void setSetupActivity(SetupActivity setupActivity); void setSetupActivity(SetupActivity setupActivity);
@@ -13,20 +14,17 @@ public interface SetupController extends PasswordController {
void setPassword(String password); void setPassword(String password);
/** float estimatePasswordStrength(String password);
* This should be called after the author name has been set.
*/
void showPasswordFragment();
/** /**
* This should be called after the author name and the password have been * This should be called after the author name and the password have been
* set. * set. It decides whether to ask for doze exception or create the account
* right away.
*/ */
void showDozeFragment(); void showDozeOrCreateAccount();
/**
* This should be called after the author name and the password have been
* set.
*/
void createAccount(); void createAccount();
void createAccount(ResultHandler<Void> resultHandler);
} }

View File

@@ -9,12 +9,10 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler; import org.briarproject.briar.android.controller.handler.UiResultHandler;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -22,11 +20,10 @@ import javax.inject.Inject;
public class SetupControllerImpl extends PasswordControllerImpl public class SetupControllerImpl extends PasswordControllerImpl
implements SetupController { implements SetupController {
private static final Logger LOG =
Logger.getLogger(SetupControllerImpl.class.getName());
@Nullable @Nullable
private volatile SetupActivity setupActivity; private String authorName, password;
@Nullable
private SetupActivity setupActivity;
@Inject @Inject
SetupControllerImpl(SharedPreferences briarPrefs, SetupControllerImpl(SharedPreferences briarPrefs,
@@ -44,7 +41,6 @@ public class SetupControllerImpl extends PasswordControllerImpl
@Override @Override
public boolean needToShowDozeFragment() { public boolean needToShowDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException(); if (setupActivity == null) throw new IllegalStateException();
return DozeView.needsToBeShown(setupActivity) || return DozeView.needsToBeShown(setupActivity) ||
HuaweiView.needsToBeShown(setupActivity); HuaweiView.needsToBeShown(setupActivity);
@@ -52,35 +48,28 @@ public class SetupControllerImpl extends PasswordControllerImpl
@Override @Override
public void setAuthorName(String authorName) { public void setAuthorName(String authorName) {
SetupActivity setupActivity = this.setupActivity; this.authorName = authorName;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setAuthorName(authorName);
}
@Override
public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setPassword(password);
}
@Override
public void showPasswordFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException(); if (setupActivity == null) throw new IllegalStateException();
setupActivity.showPasswordFragment(); setupActivity.showPasswordFragment();
} }
@Override @Override
public void showDozeFragment() { public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity; this.password = password;
}
@Override
public void showDozeOrCreateAccount() {
if (setupActivity == null) throw new IllegalStateException(); if (setupActivity == null) throw new IllegalStateException();
setupActivity.showDozeFragment(); if (needToShowDozeFragment()) {
setupActivity.showDozeFragment();
} else {
createAccount();
}
} }
@Override @Override
public void createAccount() { public void createAccount() {
SetupActivity setupActivity = this.setupActivity;
UiResultHandler<Void> resultHandler = UiResultHandler<Void> resultHandler =
new UiResultHandler<Void>(setupActivity) { new UiResultHandler<Void>(setupActivity) {
@Override @Override
@@ -93,24 +82,16 @@ public class SetupControllerImpl extends PasswordControllerImpl
createAccount(resultHandler); createAccount(resultHandler);
} }
// Package access for testing @Override
void createAccount(ResultHandler<Void> resultHandler) { public void createAccount(ResultHandler<Void> resultHandler) {
SetupActivity setupActivity = this.setupActivity; if (authorName == null || password == null)
if (setupActivity == null) throw new IllegalStateException(); throw new IllegalStateException();
String authorName = setupActivity.getAuthorName();
if (authorName == null) throw new IllegalStateException();
String password = setupActivity.getPassword();
if (password == null) throw new IllegalStateException();
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
LOG.info("Creating account");
AndroidUtils.logDataDirContents(setupActivity);
databaseConfig.setLocalAuthorName(authorName); databaseConfig.setLocalAuthorName(authorName);
SecretKey key = crypto.generateSecretKey(); SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key); databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password); String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex); storeEncryptedDatabaseKey(hex);
LOG.info("Created account");
AndroidUtils.logDataDirContents(setupActivity);
resultHandler.onResult(null); resultHandler.onResult(null);
}); });
} }

View File

@@ -28,7 +28,7 @@ import javax.inject.Inject;
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.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD;
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.controller.BriarControllerImpl.DOZE_ASK_AGAIN; import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;

View File

@@ -18,8 +18,6 @@ import org.acra.builder.ReportBuilder;
import org.acra.builder.ReportPrimer; import org.acra.builder.ReportPrimer;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.BuildConfig; import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -30,8 +28,6 @@ import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
@@ -73,16 +69,6 @@ public class BriarReportPrimer implements ReportPrimer {
public Map<String, String> call() { public Map<String, String> call() {
Map<String, String> customData = new LinkedHashMap<>(); Map<String, String> customData = new LinkedHashMap<>();
// Log
BriarApplication app =
(BriarApplication) ctx.getApplicationContext();
StringBuilder sb = new StringBuilder();
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : app.getRecentLogRecords()) {
sb.append(formatter.format(record)).append('\n');
}
customData.put("Log", sb.toString());
// System memory // System memory
Object o = ctx.getSystemService(ACTIVITY_SERVICE); Object o = ctx.getSystemService(ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) o; ActivityManager am = (ActivityManager) o;
@@ -237,10 +223,9 @@ public class BriarReportPrimer implements ReportPrimer {
customData.put("Bluetooth LE status", btLeStatus); customData.put("Bluetooth LE status", btLeStatus);
} }
if (bt != null) { if (bt != null)
customData.put("Bluetooth address", customData.put("Bluetooth address",
scrubMacAddress(bt.getAddress())); scrubMacAddress(bt.getAddress()));
}
String btSettingsAddr; String btSettingsAddr;
try { try {
btSettingsAddr = Settings.Secure.getString( btSettingsAddr = Settings.Secure.getString(

View File

@@ -6,7 +6,7 @@ import android.support.annotation.NonNull;
import org.acra.collector.CrashReportData; import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender; import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException; import org.acra.sender.ReportSenderException;
import org.acra.util.JSONReportBuilder.JSONReportException; import org.acra.util.JSONReportBuilder;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.android.AndroidComponent; import org.briarproject.briar.android.AndroidComponent;
@@ -37,7 +37,7 @@ public class BriarReportSender implements ReportSender {
String crashReport; String crashReport;
try { try {
crashReport = errorContent.toJSON().toString(); crashReport = errorContent.toJSON().toString();
} catch (JSONReportException e) { } catch (JSONReportBuilder.JSONReportException e) {
throw new ReportSenderException("Couldn't create JSON", e); throw new ReportSenderException("Couldn't create JSON", e);
} }
try { try {

View File

@@ -12,7 +12,6 @@ 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;
import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceGroup;
import android.widget.Toast; import android.widget.Toast;
import org.acra.ACRA; import org.acra.ACRA;
@@ -150,19 +149,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
return true; return true;
}); });
if (IS_DEBUG_BUILD) { Preference testData = findPreference("pref_key_test_data");
findPreference("pref_key_explode").setOnPreferenceClickListener( if (!IS_DEBUG_BUILD) {
preference -> { testData.setVisible(false);
throw new RuntimeException("Boom!");
}
);
} else {
findPreference("pref_key_explode").setVisible(false);
findPreference("pref_key_test_data").setVisible(false);
PreferenceGroup testing =
findPreference("pref_key_explode").getParent();
if (testing == null) throw new AssertionError();
testing.setVisible(false);
} }
loadSettings(); loadSettings();

View File

@@ -8,7 +8,6 @@ import android.support.v7.preference.PreferenceManager;
import android.transition.Fade; import android.transition.Fade;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.AndroidUtils;
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.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
@@ -20,7 +19,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
public class SplashScreenActivity extends BaseActivity { public class SplashScreenActivity extends BaseActivity {
@@ -45,11 +44,9 @@ public class SplashScreenActivity extends BaseActivity {
setContentView(R.layout.splash); setContentView(R.layout.splash);
if (configController.accountSignedIn()) { if (configController.accountSignedIn()) {
LOG.info("Already signed in, not showing splash screen");
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
finish(); finish();
} else { } else {
LOG.info("Showing splash screen");
new Handler().postDelayed(() -> { new Handler().postDelayed(() -> {
startNextActivity(); startNextActivity();
supportFinishAfterTransition(); supportFinishAfterTransition();
@@ -67,12 +64,9 @@ public class SplashScreenActivity extends BaseActivity {
LOG.info("Expired"); LOG.info("Expired");
startActivity(new Intent(this, ExpiredActivity.class)); startActivity(new Intent(this, ExpiredActivity.class));
} else { } else {
AndroidUtils.logDataDirContents(this);
if (configController.accountExists()) { if (configController.accountExists()) {
LOG.info("Account exists");
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
} else { } else {
LOG.info("Account does not exist");
configController.deleteAccount(this); configController.deleteAccount(this);
startActivity(new Intent(this, SetupActivity.class)); startActivity(new Intent(this, SetupActivity.class));
} }
@@ -80,10 +74,8 @@ public class SplashScreenActivity extends BaseActivity {
} }
private void setPreferencesDefaults() { private void setPreferencesDefaults() {
androidExecutor.runOnBackgroundThread(() -> { androidExecutor.runOnBackgroundThread(() ->
PreferenceManager.setDefaultValues(SplashScreenActivity.this, PreferenceManager.setDefaultValues(SplashScreenActivity.this,
R.xml.panic_preferences, false); R.xml.panic_preferences, false));
LOG.info("Finished setting panic preference defaults");
});
} }
} }

View File

@@ -0,0 +1,176 @@
package org.briarproject.briar.android.test;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import android.widget.Button;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.api.test.TestDataCreator;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;
import javax.inject.Inject;
import fr.bmartel.speedtest.SpeedTestReport;
import fr.bmartel.speedtest.SpeedTestSocket;
import fr.bmartel.speedtest.inter.ISpeedTestListener;
import fr.bmartel.speedtest.model.SpeedTestError;
public class PollingTestActivity extends BriarActivity {
private static final Logger LOG =
Logger.getLogger(PollingTestActivity.class.getName());
// Add one contact after each round up to:
private static int NUMBER_OF_CONTACTS = 10;
// Download x times per #contacts
private static int NUMBER_OF_DOWNLOADS = 1;
// File size to download. One of (5,10,50,100,500)MB.
private static int DOWNLOAD_SIZE = 10;
// Time to wait between each round in ms.
private static int PAUSE = 5000;
// Socket timeout in ms
private static int SO_TIMEOUT = 60000;
@Inject
TestDataCreator testDataCreator;
@Inject
AndroidExecutor executor;
@Inject
EventBus eventBus;
GraphView graphView;
@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_polling);
Button button = findViewById(R.id.run_test_button);
button.setOnClickListener(v -> {
runTest();
});
graphView = findViewById(R.id.graph);
graphView.getGridLabelRenderer()
.setHorizontalAxisTitle("Number of contacts");
graphView.getGridLabelRenderer().setVerticalAxisTitle("MBit/s");
graphView.getViewport().setXAxisBoundsManual(true);
graphView.getViewport().setMinX(0);
graphView.getViewport().setMaxX(NUMBER_OF_CONTACTS);
}
private void runTest() {
executor.runOnBackgroundThread(() -> {
LineGraphSeries<DataPoint> series = new LineGraphSeries<>();
graphView.addSeries(series);
for (int i = 0; i < NUMBER_OF_CONTACTS; i++) {
if (i != 0) {
createContact();
// Wait to let the previous round of polling settle.
synchronized (this) {
try {
wait(PAUSE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Reset BT polling.
eventBus.broadcast(new TransportDisabledEvent(
BluetoothConstants.ID));
eventBus.broadcast(
new TransportEnabledEvent(BluetoothConstants.ID));
LOG.info("\n###########\nRound " + i + " (Contacts)");
double y = run();
series.appendData(new DataPoint(i, y), false,
NUMBER_OF_CONTACTS);
}
});
}
private double run() {
SpeedTestReport reports[] = new SpeedTestReport[3];
double average = 0;
for (int i = 0; i < NUMBER_OF_DOWNLOADS; i++) {
final CountDownLatch cl = new CountDownLatch(1);
SpeedTestSocket speedTestSocket = new SpeedTestSocket();
speedTestSocket.setSocketTimeout(SO_TIMEOUT);
int finalI = i;
speedTestSocket.addSpeedTestListener(new ISpeedTestListener() {
@Override
public void onCompletion(SpeedTestReport report) {
LOG.info("[COMPLETED]: rate in bit/s : " +
report.getTransferRateBit() + " in " +
(report.getReportTime() -
report.getStartTime()) + "ms");
reports[finalI] = report;
cl.countDown();
}
@Override
public void onProgress(float percent,
SpeedTestReport report) {
/* LOG.info("[PROGRESS] rate in bit/s : " +
report.getTransferRateBit());
*/
}
@Override
public void onError(SpeedTestError speedTestError,
String errorMessage) {
LOG.info("Error: " + speedTestError.name() + " : " +
errorMessage);
}
});
LOG.info("Download " + (i + 1) + " of " + NUMBER_OF_DOWNLOADS);
speedTestSocket
.startDownload(
"http://ikoula.testdebit.info/" + DOWNLOAD_SIZE +
"M.iso");
try {
cl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
average += reports[i].getTransferRateBit().doubleValue() / 1000000;
}
return average / NUMBER_OF_DOWNLOADS;
}
private void createContact() {
testDataCreator.createTestData(1, 0, 0, 0, 0);
}
@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

@@ -46,7 +46,7 @@ import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/run_test_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Run test"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<com.jjoe64.graphview.GraphView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/run_test_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/graph" />
</android.support.constraint.ConstraintLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider">
</View>

View File

@@ -20,12 +20,10 @@
<ImageView <ImageView
android:id="@+id/checkImage" android:id="@+id/checkImage"
android:layout_width="24dp" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="wrap_content"
android:layout_margin="8dp" android:src="@drawable/contact_disconnected"
android:src="@drawable/ic_check_white"
android:tint="?colorControlNormal" android:tint="?colorControlNormal"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/button" app:layout_constraintBottom_toBottomOf="@+id/button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/button" app:layout_constraintTop_toTopOf="@+id/button"
@@ -36,7 +34,9 @@
style="@style/BriarButton.Default" style="@style/BriarButton.Default"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toStartOf="@+id/helpButton" app:layout_constraintEnd_toStartOf="@+id/helpButton"
app:layout_constraintStart_toEndOf="@+id/checkImage" app:layout_constraintStart_toEndOf="@+id/checkImage"
app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintTop_toBottomOf="@+id/textView"
@@ -44,13 +44,11 @@
<ImageButton <ImageButton
android:id="@+id/helpButton" android:id="@+id/helpButton"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButton.Default"
android:layout_width="24dp" android:layout_width="48dp"
android:layout_height="24dp" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:contentDescription="@string/help" android:contentDescription="@string/help"
android:src="@drawable/ic_help_outline_white" android:src="@drawable/ic_help_outline_white"
android:tint="@color/briar_button_positive"
app:layout_constraintBottom_toBottomOf="@+id/button" app:layout_constraintBottom_toBottomOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/button"/> app:layout_constraintTop_toTopOf="@+id/button"/>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/briar_blue_light"/>

View File

@@ -1,147 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Afáyate\'n Briar</string>
<string name="setup_name_explanation">El to alcuñu apaecerá cabo cualquier conteníu que publiques.Nun puede cambiase después de crear la cuenta.</string>
<string name="setup_next">Siguiente</string>
<string name="setup_password_intro">Escueye una contraseña</string>
<string name="setup_doze_title">Conexones de fondu</string>
<string name="setup_doze_button">Permitir conexones</string>
<string name="choose_nickname">Escueyi un alcuñu</string>
<string name="choose_password">Escueyi una contraseña</string>
<string name="confirm_password">Confirma la contraseña</string>
<string name="name_too_long">El nome ye enforma llargu</string>
<string name="password_too_weak">La contraseña ye enforma feble</string>
<string name="passwords_do_not_match">Les contraseñes nun casen</string>
<string name="create_account_button">Crear cuenta</string>
<string name="more_info">Más información</string>
<string name="don_t_ask_again">Nun volver a entrugar</string>
<string name="setup_huawei_button">Protexer Briar</string>
<string name="warning_dozed">%s nun pudo executase\'n segundu planu</string>
<!--Login-->
<string name="enter_password">Escribi la contraseña</string>
<string name="try_again">Contraseña enquivocada, tenta otra vuelta</string>
<string name="sign_in_button">Aniciar sesión</string>
<string name="forgotten_password">Escaecí la contraseña</string>
<string name="dialog_title_lost_password">Contraseña perdida</string>
<string name="dialog_message_lost_password">La cuenta de Briar guárdase cifrada nel preséu, non na nube, de manera que nun podemos reaniciala.. ¿Quies desaniciar la cuenta y principiar de cero?\n\nAtención: Les tos identidaes, contautos y mensaxes perderánse de mou permanente.</string>
<string name="startup_failed_notification_title">Briar nun pudo arrancar</string>
<string name="startup_failed_activity_title">Fallu nel arranque de Briar</string>
<string name="expiry_date_reached">Esti software caducó.\n¡Gracies por probalu!</string>
<string name="download_briar">Pa siguir usando Briar, descarga la versión 1.0.</string>
<string name="create_new_account">Tendrás de crear una cuenta nueva, pero puedes usar el mesmu alcuñu.</string>
<string name="download_briar_button">Descargar Briar 1.0</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Abrir el caxón de navegación</string>
<string name="nav_drawer_close_description">Zarrar el caxón de navegación</string>
<string name="contact_list_button">Contactos</string>
<string name="groups_button">Grupos privaos</string>
<string name="forums_button">Foros</string>
<string name="blogs_button">Blogs</string>
<string name="settings_button">Preferencies</string>
<string name="sign_out_button">Colar</string>
<!--Transports-->
<string name="transport_tor">Internet</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Sesión aniciada</string>
<string name="ongoing_notification_text">Toca p\'abrir Briar.</string>
<plurals name="private_message_notification_text">
<item quantity="one">Mensaxes priváu nuevu.</item>
<item quantity="other">%d mensaxes privaos nuevos.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Mensaxe de grupu nuevu.</item>
<item quantity="other">%d mensaxes de grupu nuevos.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Mensaxe de foru nuevu.</item>
<item quantity="other">%d mensaxes de foru nuevos.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Una publicación del blog nueva.</item>
<item quantity="other">%d publicaciones del blog nueves.</item>
</plurals>
<!--Misc-->
<string name="now">agora</string>
<string name="show">Amosar</string>
<string name="hide">Anubrir</string>
<string name="ok">Aceutar</string>
<string name="cancel">Encaboxar</string>
<string name="got_it">Entendílo</string>
<string name="delete">Desaniciar</string>
<string name="accept">Aceutar</string>
<string name="decline">Refugar</string>
<string name="options">Opciones</string>
<string name="online">En llinia</string>
<string name="offline">Sin conexón</string>
<string name="send">Unviar</string>
<string name="allow">Permitir</string>
<string name="open">Abrir</string>
<string name="no_data">Sin datos</string>
<string name="ellipsis"></string>
<string name="fix">Iguar</string>
<string name="help">Ayuda</string>
<!--Contacts and Private Conversations-->
<!--Adding Contacts-->
<!--Introductions-->
<!--Private Groups-->
<plurals name="messages">
<item quantity="one">%d mensaxe</item>
<item quantity="other">%d mensaxes</item>
</plurals>
<string name="groups_remove">Desaniciar</string>
<string name="groups_create_group_title">Crear Grupu priváu</string>
<string name="groups_create_group_button">Crear Grupu</string>
<string name="groups_create_group_invitation_button">Unviar Invitación</string>
<string name="groups_member_list">Llista de miembros</string>
<string name="groups_invite_members">Invitar a miembros</string>
<string name="groups_member_created_you">Creasti\'\'l grupu</string>
<string name="groups_member_created">%s creó\'\'l grupu</string>
<string name="groups_dissolve_button">Disolver</string>
<string name="groups_dissolved_dialog_title">Disolvióse\'\'l grupu</string>
<!--Private Group Invitations-->
<string name="groups_invitations_invitation_sent">Invitasti a %1$s a xunise al grupu \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s invitóte a xunite al grupu \"%2$s\".</string>
<!--Private Groups Revealing Contacts-->
<!--Forums-->
<string name="create_forum_title">Crear foru</string>
<string name="choose_forum_hint">Escueyi un nome pal foru</string>
<string name="create_forum_button">Crear foru</string>
<string name="forum_created_toast">Creóse\'l foru</string>
<string name="no_forum_posts">Nun hai mensaxes qu\'amosar</string>
<!--Forum Sharing-->
<string name="nobody">Naide</string>
<!--Blogs-->
<string name="blogs_other_blog_empty_state">Nun hai mensaxes qu\'amosar</string>
<string name="read_more">lleer más</string>
<string name="blogs_publish_blog_post">Publicar</string>
<string name="blogs_remove_blog_ok">Desaniciar</string>
<!--Blog Sharing-->
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importar canal RSS</string>
<string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_remove_feed_ok">Desaniciar</string>
<!--Settings Network-->
<string name="network_settings_title">Redes</string>
<string name="tor_network_setting">Coneutar vía Tor</string>
<string name="tor_network_setting_never">Nunca</string>
<string name="tor_network_setting_wifi">Sólo al usar Wi-Fi</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">Seguridá</string>
<string name="panic_setting">Configuración del Botón d\'espantu</string>
<string name="panic_setting_title">Botón d\'espantu</string>
<string name="lock_setting_title">Colar</string>
<string name="uninstall_setting_title">Desinstalar Briar</string>
<!--Settings Notifications-->
<string name="notification_settings_title">Avisos</string>
<string name="notify_private_messages_setting_title">Mensaxes privaos</string>
<!--Settings Feedback-->
<!--Link Warning-->
<!--Crash Reporter-->
<!--Sign Out-->
<!--Screen Filters & Tapjacking-->
<!--Permission Requests-->
</resources>

View File

@@ -326,4 +326,5 @@
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Открит е овърлей на екрана</string> <string name="screen_filter_title">Открит е овърлей на екрана</string>
<!--Permission Requests--> <!--Permission Requests-->
<!--Low Memory Notification-->
</resources> </resources>

View File

@@ -39,30 +39,18 @@
<string name="ongoing_notification_text">Touchit da zigeriñ Briar.</string> <string name="ongoing_notification_text">Touchit da zigeriñ Briar.</string>
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
<item quantity="one">Kemennadenn prevez nevez</item> <item quantity="one">Kemennadenn prevez nevez</item>
<item quantity="two">%d a gemennadennoù prevez nevez. </item>
<item quantity="few">%d a gemennadennoù prevez nevez. </item>
<item quantity="many">%d a gemennadennoù prevez nevez. </item>
<item quantity="other">%d a gemennadennoù prevez nevez. </item> <item quantity="other">%d a gemennadennoù prevez nevez. </item>
</plurals> </plurals>
<plurals name="group_message_notification_text"> <plurals name="group_message_notification_text">
<item quantity="one">Kemennadenn strollad nevez.</item> <item quantity="one">Kemennadenn strollad nevez.</item>
<item quantity="two">%d a gemennadennoù strollad nevez.</item>
<item quantity="few">%d a gemennadennoù strollad nevez.</item>
<item quantity="many">%d a gemennadennoù strollad nevez.</item>
<item quantity="other">%d a gemennadennoù strollad nevez.</item> <item quantity="other">%d a gemennadennoù strollad nevez.</item>
</plurals> </plurals>
<plurals name="forum_post_notification_text"> <plurals name="forum_post_notification_text">
<item quantity="one">Postadenn forom nevez.</item> <item quantity="one">Postadenn forom nevez.</item>
<item quantity="two">%da bostadennoù forom nevez.</item>
<item quantity="few">%da bostadennoù forom nevez.</item>
<item quantity="many">%da bostadennoù forom nevez.</item>
<item quantity="other">%da bostadennoù forom nevez.</item> <item quantity="other">%da bostadennoù forom nevez.</item>
</plurals> </plurals>
<plurals name="blog_post_notification_text"> <plurals name="blog_post_notification_text">
<item quantity="one">Postadenn blog nevez.</item> <item quantity="one">Postadenn blog nevez.</item>
<item quantity="two">%d a bostadennoù blog nevez.</item>
<item quantity="few">%d a bostadennoù blog nevez.</item>
<item quantity="many">%d a bostadennoù blog nevez.</item>
<item quantity="other">%d a bostadennoù blog nevez.</item> <item quantity="other">%d a bostadennoù blog nevez.</item>
</plurals> </plurals>
<!--Misc--> <!--Misc-->
@@ -111,18 +99,12 @@
<string name="introduction_response_error">Fazi en ur respont d\'an digoradur</string> <string name="introduction_response_error">Fazi en ur respont d\'an digoradur</string>
<plurals name="introduction_notification_text"> <plurals name="introduction_notification_text">
<item quantity="one">Darempred nevez ouzhpennet.</item> <item quantity="one">Darempred nevez ouzhpennet.</item>
<item quantity="two">%d a zarempredoù nevez ouzhpennet.</item>
<item quantity="few">%d a zarempredoù nevez ouzhpennet.</item>
<item quantity="many">%d a zarempredoù nevez ouzhpennet.</item>
<item quantity="other">%d a zarempredoù nevez ouzhpennet.</item> <item quantity="other">%d a zarempredoù nevez ouzhpennet.</item>
</plurals> </plurals>
<!--Private Groups--> <!--Private Groups-->
<string name="groups_created_by">Graet gant %s</string> <string name="groups_created_by">Graet gant %s</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d kemennadenn</item> <item quantity="one">%d kemennadenn</item>
<item quantity="two">%d a gemennadennoù</item>
<item quantity="few">%d a gemennadennoù</item>
<item quantity="many">%d a gemennadennoù</item>
<item quantity="other">%d a gemennadennoù</item> <item quantity="other">%d a gemennadennoù</item>
</plurals> </plurals>
<string name="groups_group_is_empty">Leun eo ar strollad-mañ</string> <string name="groups_group_is_empty">Leun eo ar strollad-mañ</string>
@@ -167,4 +149,5 @@
<!--Sign Out--> <!--Sign Out-->
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
<!--Permission Requests--> <!--Permission Requests-->
<!--Low Memory Notification-->
</resources> </resources>

View File

@@ -393,4 +393,7 @@
<string name="permission_camera_denied_toast">No s\'ha concedit el permís de la càmera</string> <string name="permission_camera_denied_toast">No s\'ha concedit el permís de la càmera</string>
<string name="qr_code">Codi QR</string> <string name="qr_code">Codi QR</string>
<string name="show_qr_code_fullscreen">Mostra el codi QR a pantalla completa</string> <string name="show_qr_code_fullscreen">Mostra el codi QR a pantalla completa</string>
<!--Low Memory Notification-->
<string name="low_memory_shutdown_notification_title">Has sortit de Briar</string>
<string name="low_memory_shutdown_notification_text">Has sortit de Briar a causa de falta de memòria.</string>
</resources> </resources>

View File

@@ -36,7 +36,6 @@
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="one">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item> <item quantity="one">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
<item quantity="few">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item> <item quantity="few">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
<item quantity="many">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
<item quantity="other">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item> <item quantity="other">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
</plurals> </plurals>
<string name="expiry_update">Datum expirace pro testování bylo prodlouženo. Váš účet nyní bude expirovat po %d dnech.</string> <string name="expiry_update">Datum expirace pro testování bylo prodlouženo. Váš účet nyní bude expirovat po %d dnech.</string>
@@ -60,25 +59,21 @@
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
<item quantity="one">Nová soukromá zpráva</item> <item quantity="one">Nová soukromá zpráva</item>
<item quantity="few">%d nových soukromých zpráv.</item> <item quantity="few">%d nových soukromých zpráv.</item>
<item quantity="many">%d nových soukromých zpráv.</item>
<item quantity="other">%d nových soukromých zpráv.</item> <item quantity="other">%d nových soukromých zpráv.</item>
</plurals> </plurals>
<plurals name="group_message_notification_text"> <plurals name="group_message_notification_text">
<item quantity="one">Nová zpráva ve skupině.</item> <item quantity="one">Nová zpráva ve skupině.</item>
<item quantity="few">%d nových zpráv ve skupině. </item> <item quantity="few">%d nových zpráv ve skupině. </item>
<item quantity="many">%d nových zpráv ve skupině.</item>
<item quantity="other">%d nových zpráv ve skupině.</item> <item quantity="other">%d nových zpráv ve skupině.</item>
</plurals> </plurals>
<plurals name="forum_post_notification_text"> <plurals name="forum_post_notification_text">
<item quantity="one">Nový příspěvek ve fóru.</item> <item quantity="one">Nový příspěvek ve fóru.</item>
<item quantity="few">%dnových příspěvků ve fóru. </item> <item quantity="few">%dnových příspěvků ve fóru. </item>
<item quantity="many">%d nových příspěvků ve fóru.</item>
<item quantity="other">%d nových příspěvků ve fóru.</item> <item quantity="other">%d nových příspěvků ve fóru.</item>
</plurals> </plurals>
<plurals name="blog_post_notification_text"> <plurals name="blog_post_notification_text">
<item quantity="one">Nový příspěvek v blogu.</item> <item quantity="one">Nový příspěvek v blogu.</item>
<item quantity="few">%dnových příspěvků v blogu. </item> <item quantity="few">%dnových příspěvků v blogu. </item>
<item quantity="many">%d nových příspěvků v blogu.</item>
<item quantity="other">%d nových příspěvků v blogu.</item> <item quantity="other">%d nových příspěvků v blogu.</item>
</plurals> </plurals>
<!--Misc--> <!--Misc-->
@@ -149,7 +144,6 @@
<plurals name="introduction_notification_text"> <plurals name="introduction_notification_text">
<item quantity="one">Nový kontakt byl přidán.</item> <item quantity="one">Nový kontakt byl přidán.</item>
<item quantity="few">Bylo přidáno %d nových kontaktů.</item> <item quantity="few">Bylo přidáno %d nových kontaktů.</item>
<item quantity="many">%d nových kontaktů bylo přidáno.</item>
<item quantity="other">%d nových kontaktů bylo přidáno.</item> <item quantity="other">%d nových kontaktů bylo přidáno.</item>
</plurals> </plurals>
<!--Private Groups--> <!--Private Groups-->
@@ -157,7 +151,6 @@
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d zpráva</item> <item quantity="one">%d zpráva</item>
<item quantity="few">%d zpráv</item> <item quantity="few">%d zpráv</item>
<item quantity="many">%d zpráv</item>
<item quantity="other">%d zpráv</item> <item quantity="other">%d zpráv</item>
</plurals> </plurals>
<string name="groups_group_is_empty">Tato skupina je prázdná</string> <string name="groups_group_is_empty">Tato skupina je prázdná</string>
@@ -193,7 +186,6 @@
<plurals name="groups_invitations_open"> <plurals name="groups_invitations_open">
<item quantity="one">%d otevřená skupinová pozvánka</item> <item quantity="one">%d otevřená skupinová pozvánka</item>
<item quantity="few">%d otevřených skupinových pozvánek</item> <item quantity="few">%d otevřených skupinových pozvánek</item>
<item quantity="many">%d otevřených skupinových pozvánek</item>
<item quantity="other">%d otevřených skupinových pozvánek</item> <item quantity="other">%d otevřených skupinových pozvánek</item>
</plurals> </plurals>
<string name="groups_invitations_response_accepted_sent">Přijali jste pozvání do skupiny od %s.</string> <string name="groups_invitations_response_accepted_sent">Přijali jste pozvání do skupiny od %s.</string>
@@ -217,7 +209,6 @@
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d příspěvek</item> <item quantity="one">%d příspěvek</item>
<item quantity="few">%d příspěvků</item> <item quantity="few">%d příspěvků</item>
<item quantity="many">%d příspěvků</item>
<item quantity="other">%d příspěvků</item> <item quantity="other">%d příspěvků</item>
</plurals> </plurals>
<string name="forum_new_entry_posted">Záznam byl na fóru zveřejněn</string> <string name="forum_new_entry_posted">Záznam byl na fóru zveřejněn</string>
@@ -249,7 +240,6 @@
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d fórum sdíleno kontakty</item> <item quantity="one">%d fórum sdíleno kontakty</item>
<item quantity="few">%d fór sdíleno kontakty </item> <item quantity="few">%d fór sdíleno kontakty </item>
<item quantity="many">%d fór sdíleno kontakty</item>
<item quantity="other">%d fór sdíleno kontakty</item> <item quantity="other">%d fór sdíleno kontakty</item>
</plurals> </plurals>
<string name="nobody">Nikdo</string> <string name="nobody">Nikdo</string>
@@ -372,4 +362,5 @@
<string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string> <string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string>
<string name="permission_camera_denied_body">Odmítli jste udělit oprávnění přístupu k fotoaparátu, avšak pro přidání kontaktů je nutné použití fotoaparátu.\n\nZvažte prosím, opětovné udělení přístupu.</string> <string name="permission_camera_denied_body">Odmítli jste udělit oprávnění přístupu k fotoaparátu, avšak pro přidání kontaktů je nutné použití fotoaparátu.\n\nZvažte prosím, opětovné udělení přístupu.</string>
<string name="permission_camera_denied_toast">Oprávnění pro přístup k fotoaparátu nebylo uděleno</string> <string name="permission_camera_denied_toast">Oprávnění pro přístup k fotoaparátu nebylo uděleno</string>
<!--Low Memory Notification-->
</resources> </resources>

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