Compare commits

..

2 Commits

Author SHA1 Message Date
akwizgran
3a13adcffb Rewrap some lines. 2021-11-04 15:49:44 +00:00
akwizgran
f4d885b647 Add checkstyle plugin. 2021-11-04 15:48:06 +00:00
434 changed files with 3951 additions and 17692 deletions

View File

@@ -32,9 +32,8 @@ test:
extends: .base-test extends: .base-test
stage: test stage: test
script: script:
- ./gradlew -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest - ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew -Djava.security.egd=file:/dev/urandom assembleOfficialDebug :briar-headless:linuxJars - ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always when: always
@@ -62,7 +61,7 @@ android test:
when: on_failure when: on_failure
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_PIPELINE_SOURCE == "schedule"'
when: manual when: on_success
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes: changes:
- briar-android/**/* - briar-android/**/*
@@ -85,44 +84,35 @@ test_reproducible:
.optional_tests: .optional_tests:
stage: optional_tests stage: optional_tests
extends: .base-test before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
bridge test: bridge test:
extends: .optional_tests extends: .optional_tests
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success when: on_success
allow_failure: false allow_failure: true
- if: '$CI_COMMIT_TAG == null' - if: '$CI_COMMIT_TAG == null'
when: manual when: manual
allow_failure: true allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
mailbox integration test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true # TODO figure out how not to allow failure while leaving this optional
script:
# start mailbox
- cd /opt && git clone --depth 1 https://code.briarproject.org/briar/briar-mailbox.git briar-mailbox
- cd briar-mailbox
- mkdir -p /root/.local/share # create directory that mailbox (currently) expects to exist
- ./gradlew run --args="--debug --setup-token 54686973206973206120736574757020746f6b656e20666f722042726961722e" &
# run mailbox integration test once mailbox has started
- cd "$CI_PROJECT_DIR"
- bramble-core/src/test/bash/wait-for-mailbox.sh
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
pre_release_tests: pre_release_tests:
extends: .optional_tests extends: .optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
only: only:
- tags - tags

View File

@@ -22,15 +22,6 @@ our site.
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home) [Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
## Reproducible builds
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
to ease the task of verifying that the published APK binaries
include nothing but our publicly available source code.
You can either use those images or use them as a blueprint to build your own environment
for reproduction.
## Donate ## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/) [![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/)

View File

@@ -1,6 +1,7 @@
import com.android.build.gradle.tasks.MergeResources import com.android.build.gradle.tasks.MergeResources
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'checkstyle'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
@@ -15,8 +16,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10408 versionCode 10401
versionName "1.4.8" versionName "1.4.1"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,8 +43,8 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor "org.briarproject:tor-android:$tor_version" tor 'org.briarproject:tor-android:0.3.5.15'
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version" tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -53,7 +54,7 @@ dependencies {
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-legacy:$jmock_version"
} }
def torBinariesDir = 'src/main/res/raw' def torBinariesDir = 'src/main/res/raw'
@@ -70,6 +71,11 @@ clean.dependsOn cleanTorBinaries
task unpackTorBinaries { task unpackTorBinaries {
doLast { doLast {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
include 'geoip.zip'
}
configurations.tor.each { outer -> configurations.tor.each { outer ->
zipTree(outer).each { inner -> zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) { if (inner.name.endsWith('_arm_pie.zip')) {

View File

@@ -1,8 +1,6 @@
# Keep the H2 classes that are loaded via reflection -keep,includedescriptorclasses class org.briarproject.** { *; }
-keep class org.h2.Driver { *; }
-keep class org.h2.engine.Engine { *; } -keep class org.h2.** { *; }
-keep class org.h2.store.fs.** { *; }
# Don't warn about unused dependencies of H2 classes
-dontwarn org.h2.** -dontwarn org.h2.**
-dontnote org.h2.** -dontnote org.h2.**
@@ -17,4 +15,5 @@
-dontwarn sun.misc.Unsafe -dontwarn sun.misc.Unsafe
-dontnote com.google.common.** -dontnote com.google.common.**
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl # UPnP library isn't used
-dontwarn org.bitlet.weupnp.**

View File

@@ -11,10 +11,7 @@ import android.net.LinkAddress;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
@@ -24,6 +21,7 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
@@ -40,7 +38,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -114,37 +111,15 @@ class AndroidNetworkManager implements NetworkManager, Service {
@Override @Override
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
// https://issuetracker.google.com/issues/175055271 NetworkInfo net = connectivityManager.getActiveNetworkInfo();
try { boolean connected = net != null && net.isConnected();
NetworkInfo net = connectivityManager.getActiveNetworkInfo(); boolean wifi = false, ipv6Only = false;
boolean connected = net != null && net.isConnected(); if (connected) {
boolean wifi = false, ipv6Only = false; wifi = net.getType() == TYPE_WIFI;
if (connected) { if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
wifi = net.getType() == TYPE_WIFI; else ipv6Only = areAllAvailableNetworksIpv6Only();
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
}
return new NetworkStatus(connected, wifi, ipv6Only);
} catch (SecurityException e) {
logException(LOG, WARNING, e);
// Without the ConnectivityManager we can't detect whether we have
// internet access. Assume we do, which is probably less harmful
// than assuming we don't. Likewise, assume the connection is
// IPv6-only. Fall back to the WifiManager to detect whether we
// have a wifi connection.
LOG.info("ConnectivityManager is broken, guessing connectivity");
boolean connected = true, wifi = false, ipv6Only = true;
WifiManager wm = (WifiManager) app.getSystemService(WIFI_SERVICE);
if (wm != null) {
WifiInfo info = wm.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) {
LOG.info("Connected to wifi");
wifi = true;
ipv6Only = false;
}
}
return new NetworkStatus(connected, wifi, ipv6Only);
} }
return new NetworkStatus(connected, wifi, ipv6Only);
} }
/** /**
@@ -155,29 +130,23 @@ class AndroidNetworkManager implements NetworkManager, Service {
*/ */
@TargetApi(23) @TargetApi(23)
private boolean isActiveNetworkIpv6Only() { private boolean isActiveNetworkIpv6Only() {
// https://issuetracker.google.com/issues/175055271 Network net = connectivityManager.getActiveNetwork();
try { if (net == null) {
Network net = connectivityManager.getActiveNetwork(); LOG.info("No active network");
if (net == null) {
LOG.info("No active network");
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
} catch (SecurityException e) {
logException(LOG, WARNING, e);
return false; return false;
} }
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
} }
/** /**

View File

@@ -32,22 +32,13 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
InputStream openInputStream(TransportProperties p) throws IOException { InputStream openInputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI); String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException(); if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
try { return app.getContentResolver().openInputStream(Uri.parse(uri));
return app.getContentResolver().openInputStream(Uri.parse(uri));
} catch (SecurityException e) {
throw new IOException(e);
}
} }
@Override @Override
OutputStream openOutputStream(TransportProperties p) throws IOException { OutputStream openOutputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI); String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException(); if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
try { return app.getContentResolver().openOutputStream(Uri.parse(uri));
return app.getContentResolver()
.openOutputStream(Uri.parse(uri), "wt");
} catch (SecurityException e) {
throw new IOException(e);
}
} }
} }

View File

@@ -175,24 +175,16 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@TargetApi(21) @TargetApi(21)
@Nullable @Nullable
private InetAddress getWifiClientIpv6Address() { private InetAddress getWifiClientIpv6Address() {
// https://issuetracker.google.com/issues/175055271 for (Network net : connectivityManager.getAllNetworks()) {
try { NetworkCapabilities caps =
for (Network net : connectivityManager.getAllNetworks()) { connectivityManager.getNetworkCapabilities(net);
NetworkCapabilities caps = if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
connectivityManager.getNetworkCapabilities(net); LinkProperties props = connectivityManager.getLinkProperties(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) { if (props == null) continue;
continue; for (LinkAddress linkAddress : props.getLinkAddresses()) {
} InetAddress addr = linkAddress.getAddress();
LinkProperties props = if (isIpv6LinkLocalAddress(addr)) return addr;
connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
}
} }
} catch (SecurityException e) {
logException(LOG, WARNING, e);
} }
return null; return null;
} }
@@ -235,17 +227,12 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// network's socket factory may try to connect via another network // network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() { private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault(); if (SDK_INT < 21) return SocketFactory.getDefault();
// https://issuetracker.google.com/issues/175055271 for (Network net : connectivityManager.getAllNetworks()) {
try { NetworkCapabilities caps =
for (Network net : connectivityManager.getAllNetworks()) { connectivityManager.getNetworkCapabilities(net);
NetworkCapabilities caps = if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
connectivityManager.getNetworkCapabilities(net); return net.getSocketFactory();
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory();
}
} }
} catch (SecurityException e) {
logException(LOG, WARNING, e);
} }
LOG.warning("Could not find suitable socket factory"); LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault(); return SocketFactory.getDefault();

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
@@ -11,35 +10,61 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort; import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory; import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort; import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider; import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.File; import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class AndroidTorPluginFactory extends TorPluginFactory { public class AndroidTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG =
Logger.getLogger(AndroidTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor, wakefulIoExecutor;
private final Application app; private final Application app;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager;
private final AndroidWakeLockManager wakeLockManager; private final AndroidWakeLockManager wakeLockManager;
private final Clock clock;
private final File torDirectory;
private int torSocksPort;
private int torControlPort;
@Inject @Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager, NetworkManager networkManager,
LocationUtils locationUtils, LocationUtils locationUtils,
EventBus eventBus, EventBus eventBus,
@@ -48,43 +73,77 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Clock clock, Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory, @TorDirectory File torDirectory,
@TorSocksPort int torSocksPort, @TorSocksPort int torSocksPort,
@TorControlPort int torControlPort, @TorControlPort int torControlPort) {
Application app, this.ioExecutor = ioExecutor;
AndroidWakeLockManager wakeLockManager) { this.wakefulIoExecutor = wakefulIoExecutor;
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app; this.app = app;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.wakeLockManager = wakeLockManager; this.wakeLockManager = wakeLockManager;
this.clock = clock;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
} }
@Nullable
@Override @Override
String getArchitectureForTorBinary() { public TransportId getId() {
for (String abi : getSupportedArchitectures()) { return TorConstants.ID;
if (abi.startsWith("x86_64")) return "x86_64_pie"; }
else if (abi.startsWith("x86")) return "x86_pie";
else if (abi.startsWith("arm64")) return "arm64_pie"; @Override
else if (abi.startsWith("armeabi")) return "arm_pie"; public long getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = null;
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (abi.startsWith("x86_64")) {
architecture = "x86_64";
break;
} else if (abi.startsWith("x86")) {
architecture = "x86";
break;
} else if (abi.startsWith("arm64")) {
architecture = "arm64";
break;
} else if (abi.startsWith("armeabi")) {
architecture = "arm";
break;
}
} }
return null; if (architecture == null) {
} LOG.info("Tor is not supported on this architecture");
return null;
}
// Use position-independent executable
architecture += "_pie";
@Override Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
TorPlugin createPluginInstance(Backoff backoff, MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
String architecture) { AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
return new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils, wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager, circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture, backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort, MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort); torControlPort);
eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.os.Process; import android.os.Process;
import android.os.SystemClock; import android.os.SystemClock;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AlarmListener; import org.briarproject.bramble.api.system.AlarmListener;
@@ -117,12 +116,10 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
long dueMillis = now + MILLISECONDS.convert(delay, unit); long dueMillis = now + MILLISECONDS.convert(delay, unit);
Runnable wakeful = () -> Runnable wakeful = () ->
wakeLockManager.executeWakefully(task, executor, "TaskHandoff"); wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
// Acquire the lock before scheduling the check to ensure the check Future<?> check = scheduleCheckForDueTasks(delay, unit);
// doesn't access the task queue before the task has been added ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
ScheduledTask s; cancelled);
synchronized (lock) { synchronized (lock) {
Future<?> check = scheduleCheckForDueTasks(delay, unit);
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
tasks.add(s); tasks.add(s);
} }
return s; return s;
@@ -139,7 +136,6 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
return schedule(wrapped, executor, delay, unit, cancelled); return schedule(wrapped, executor, delay, unit, cancelled);
} }
@GuardedBy("lock")
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) { private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
Runnable wakeful = () -> wakeLockManager.runWakefully( Runnable wakeful = () -> wakeLockManager.runWakefully(
this::runDueTasks, "TaskScheduler"); this::runDueTasks, "TaskScheduler");
@@ -210,7 +206,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
private final Future<?> check; private final Future<?> check;
private final AtomicBoolean cancelled; private final AtomicBoolean cancelled;
private ScheduledTask(Runnable task, long dueMillis, public ScheduledTask(Runnable task, long dueMillis,
Future<?> check, AtomicBoolean cancelled) { Future<?> check, AtomicBoolean cancelled) {
this.task = task; this.task = task;
this.dueMillis = dueMillis; this.dueMillis = dueMillis;

View File

@@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
@@ -135,8 +134,4 @@ public class AndroidUtils {
return null; return null;
} }
} }
public static boolean isUiThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
} }

View File

@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.imposters.ByteBuddyClassImposteriser; import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private AndroidAccountManager accountManager; private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() { public AndroidAccountManagerTest() {
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
app = context.mock(Application.class); app = context.mock(Application.class);
applicationInfo = new ApplicationInfo(); applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath(); applicationInfo.dataDir = testDir.getAbsolutePath();

View File

@@ -1,161 +1,123 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'antlr:antlr:2.7.7:antlr-2.7.7.jar:88fbda4b912596b9f56e8e12e580cc954bacfb51776ecfddd3e18fc1cf56dc4c',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db', 'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7', 'com.android.tools.analytics-library:protos:27.1.3:protos-27.1.3.jar:0d9e6cff60b318baac250b6f5bb076a8161103338bf2749cdf1db8a5a13a1f12',
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15', 'com.android.tools.analytics-library:shared:27.1.3:shared-27.1.3.jar:10d2a51d8f89ff4ac849888e5a9c60b10e879c30d78545ec1da4d3df7bd56ae4',
'com.android.tools.analytics-library:tracker:30.0.3:tracker-30.0.3.jar:5d0ef35bf6733e96210b5085a2a202152921bf834d345959dce1ca3369b528df', 'com.android.tools.analytics-library:tracker:27.1.3:tracker-27.1.3.jar:589b355a2ba796cbc0a2b2295737de6661f078262e5f87cd6f540b8d011e5ebb',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca', 'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:7.0.3:apksig-7.0.3.jar:012337a2803c9a30dfc41dcbc6450686ee9e5f582549f7f126479f743a343ec9', 'com.android.tools.build:apksig:4.1.3:apksig-4.1.3.jar:a851980c678ff7a6785388b9a9e8cc094788ce3c4a985ad2b19c2028fd3c631a',
'com.android.tools.build:apkzlib:7.0.3:apkzlib-7.0.3.jar:b31e53174c92db83c5cc6e7dac6734ea4e907a72e452c2bf1818dfd082c59397', 'com.android.tools.build:apkzlib:4.1.3:apkzlib-4.1.3.jar:475903065e7e83a8c1ba78d267c97a54dc5a04d768b535093850423d7b11f2c8',
'com.android.tools.build:builder-model:7.0.3:builder-model-7.0.3.jar:483f99d7494a5bed027e1e8d29111384cf535d4842f0be5a79805bd44bb68d4e', 'com.android.tools.build:builder-model:4.1.3:builder-model-4.1.3.jar:2624a1436c3ab39dd91d3ecf9409a594b0f89ea5cab255f2e9ff11f5ee03d274',
'com.android.tools.build:builder-test-api:7.0.3:builder-test-api-7.0.3.jar:f6de4bc2cef545e8367bf82d7c733829c7be3b0b3b8b09fd8c58f2150e59ab46', 'com.android.tools.build:builder-test-api:4.1.3:builder-test-api-4.1.3.jar:3d2af66726b06b53b8d6d497efcee39ff9f77eb2f8d2cce38b31502383a40d2c',
'com.android.tools.build:builder:7.0.3:builder-7.0.3.jar:c6952da0094b094c2ba0fe84c675622097c5d9b9f9beb53485b860320540cf1d', 'com.android.tools.build:builder:4.1.3:builder-4.1.3.jar:a40426cd6d68f6a722ef4950058c075e4547025e8c2fd78e732ad89f15176f84',
'com.android.tools.build:manifest-merger:30.0.3:manifest-merger-30.0.3.jar:72b346ba6318b4b6260e6e49df4bea5da2e12329ab6c2beb2269c49a9f51f178', 'com.android.tools.build:gradle-api:4.1.3:gradle-api-4.1.3.jar:11b1fb9de658bdcf9290b1c1517060d0c4d93f2b27975934989ca4ac890bc077',
'com.android.tools.ddms:ddmlib:30.0.3:ddmlib-30.0.3.jar:7a914a68ab93393657297234e2f37b22410ae9a433cba692ce8c727c9607e3bb', 'com.android.tools.build:manifest-merger:27.1.3:manifest-merger-27.1.3.jar:ce8d4009b1f1584777a7ffa1da3b0551dc316bc8e08112e442c352af70f46f2d',
'com.android.tools.external.com-intellij:intellij-core:30.0.3:intellij-core-30.0.3.jar:1ebe858d3f58eeaa8c06507f8ac0f1c7051e6c61f35a70f3c3967d5734d3abc5', 'com.android.tools.ddms:ddmlib:27.1.3:ddmlib-27.1.3.jar:8f76e8236d2b9eebf26378746dad025c4c7c056a02e133dae4ddef47b283c710',
'com.android.tools.external.com-intellij:kotlin-compiler:30.0.3:kotlin-compiler-30.0.3.jar:ed00e441f427cb4e0d418287b9da30b12b7f735f9af32e6b5d3dc960b6a742fc', 'com.android.tools.external.com-intellij:intellij-core:27.1.3:intellij-core-27.1.3.jar:652814fa099b4746fb6f10e19718e476952e8b5bac24e17d914f90650ad21808',
'com.android.tools.external.org-jetbrains:uast:30.0.3:uast-30.0.3.jar:a77801bee6ff509910e459525c9c34d7f04b066ade123547f16f1917548eadea', 'com.android.tools.external.com-intellij:kotlin-compiler:27.1.3:kotlin-compiler-27.1.3.jar:8d7a78d5efd213c5e467e42bd205582aad73ffc77ee5dc18eb1361c9af72f125',
'com.android.tools.layoutlib:layoutlib-api:30.0.3:layoutlib-api-30.0.3.jar:4caa87e9ca2e11315f650d576cd59fec1793373bc3fca3f6d53c029e7534e7c4', 'com.android.tools.external.org-jetbrains:uast:27.1.3:uast-27.1.3.jar:aea53944a1ac6a05f12297b55290e8cbecfe54c4166260cfba4405823bfe1c78',
'com.android.tools.lint:lint-api:30.0.3:lint-api-30.0.3.jar:bcecbd2f752a6560096a9029a47d1de6bd788a51bab505c5ebfba6a18524b983', 'com.android.tools.layoutlib:layoutlib-api:27.1.3:layoutlib-api-27.1.3.jar:23875ce0a8429f33a4e86cc358f658faa0ba9c576f5f05760e544b453d67d04b',
'com.android.tools.lint:lint-checks:30.0.3:lint-checks-30.0.3.jar:25a7cd42dc3ad502337f131fb8b7e873c53301db0a67b1c64dd4ae7a8eb66cec', 'com.android.tools.lint:lint-api:27.1.3:lint-api-27.1.3.jar:97666be32bcadacd944416ea334a9575ef8f4ad0c8f333151491ff4a7df43e1c',
'com.android.tools.lint:lint-gradle:30.0.3:lint-gradle-30.0.3.jar:94544d6147a809bf2fd3440e51f28a4e42e547d74aab53eefd74938cdad42c26', 'com.android.tools.lint:lint-checks:27.1.3:lint-checks-27.1.3.jar:b2d71ae84a31490fe9ff26c706163fe245b2aea98e3eb747214c1085df444708',
'com.android.tools.lint:lint-model:30.0.3:lint-model-30.0.3.jar:0b940a7f575c2ff5cbd038260f41dde686a93c672213881ead3ce8af3513b396', 'com.android.tools.lint:lint-gradle-api:27.1.3:lint-gradle-api-27.1.3.jar:e54131c287a2954e6ed78a3351e5e10e35a1da2f09ac443bf44b705c71b63a4d',
'com.android.tools.lint:lint:30.0.3:lint-30.0.3.jar:ee4f11001e0c7e3b776e0d67399ad354b19b0f168822ec2b7db47c0910ed227d', 'com.android.tools.lint:lint-gradle:27.1.3:lint-gradle-27.1.3.jar:6a79e48943649d63665db7b17dbaff7af93e94ab9b15072f1a4d90486294ee9f',
'com.android.tools:annotations:30.0.3:annotations-30.0.3.jar:5c1944982fda8555855c4f5422fabf0dc8e2306e1f5460e9ad82dae71316bc31', 'com.android.tools.lint:lint-model:27.1.3:lint-model-27.1.3.jar:acb9e792db7000e38e3c3ca21a9b14f2de6549d7a3fc92a97ffba3d06345e5bf',
'com.android.tools:common:30.0.3:common-30.0.3.jar:8751efaaf2c2ddd1f0a37526c794347def6a3057ca9fc510307c13a6cf0d036f', 'com.android.tools.lint:lint:27.1.3:lint-27.1.3.jar:5a2e69d0901a3a476a5b2d5001de755868113145f5f6aa557750cfad5389a44b',
'com.android.tools:dvlib:30.0.3:dvlib-30.0.3.jar:5affafcec390041e5afd64cb924153f5e474db47ee8ccc2f555b495083141233', 'com.android.tools:annotations:27.1.3:annotations-27.1.3.jar:904dd771883496d5dfc86619ab2555968ea4e8a29d7a5f4f7cae6fbf5429f8f5',
'com.android.tools:repository:30.0.3:repository-30.0.3.jar:0a40c6f16c506903ce2c609affd8228aceda73a69d93dfa42d4f02b8491449f6', 'com.android.tools:common:27.1.3:common-27.1.3.jar:17ab4728e3ea50f047dd5937f0faf35f2c5416962ed74891057087ddc328bf96',
'com.android.tools:sdk-common:30.0.3:sdk-common-30.0.3.jar:b45570a380360236ffee0f6bb593d66b673bad3834dfe0d6c9871fa7188ee0eb', 'com.android.tools:dvlib:27.1.3:dvlib-27.1.3.jar:cead1c0c356cbe43e6855b0330fe09ef4bec2c72e22bdb4c6e7cf7e6b1dfbc37',
'com.android.tools:sdklib:30.0.3:sdklib-30.0.3.jar:7088f20a414fab170a21e457825e14ebe099f753558e02c8acc12c67eb412162', 'com.android.tools:repository:27.1.3:repository-27.1.3.jar:99de1a178855b56b8cd91a56296f1e0a9399c445e6acc51f1d2927947cc472cb',
'com.android:signflinger:7.0.3:signflinger-7.0.3.jar:903a4536db3e96b4e1e1dc1e400eb0b91bf7866d9b39cd7ec94d75dde158f152', 'com.android.tools:sdk-common:27.1.3:sdk-common-27.1.3.jar:b591e2aa0f1be600795f5c9e2bf81cba9b052bee452fc86c3362b5dd9e427a14',
'com.android:zipflinger:7.0.3:zipflinger-7.0.3.jar:fd209c960a3eff7a339e6fcba07d5e9ef4604d1633c69ab2df987460d9804140', 'com.android.tools:sdklib:27.1.3:sdklib-27.1.3.jar:ad6c08a45fe2904d05656bdddf9f623fa5c1d16bbd7b8d6a270a0734136ae02e',
'com.beust:jcommander:1.78:jcommander-1.78.jar:7891debb84b5f83e9bd57593ebece3399abbe0fd938cf306b3534c57913b9615', 'com.android:signflinger:4.1.3:signflinger-4.1.3.jar:f3103b55ccdc8dd9ee2517eb26af93b904d41303726594372d0df59d51156e5c',
'com.github.javaparser:javaparser-core:3.17.0:javaparser-core-3.17.0.jar:23f5c982e1c7771423d37d52c774e8d2e80fd7ea7305ebe448797a96f67e6fca', 'com.android:zipflinger:4.1.3:zipflinger-4.1.3.jar:48569896c0497268308a8014c66eb0f2bace2b9e2fc9390f3012823fb86387d5',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79', 'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.6:gson-2.8.6.jar:c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f', 'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e', 'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8', 'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4', 'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb', 'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a', 'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.4:error_prone_annotations-2.3.4.jar:baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c', 'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30', 'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e', 'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26', 'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7', 'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'com.google.guava:guava:30.1-jre:guava-30.1-jre.jar:e6dd072f9d3fe02a4600688380bd422bdac184caf6fe2418cfdd0934f09432aa', 'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99', 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b', 'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd', 'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9', 'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439', 'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.puppycrawl.tools:checkstyle:8.27:checkstyle-8.27.jar:26c81958a112ebdfc5d7b40507bbfc8f15f606fea1e55ef5675609ddb41a6053',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0', 'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce', 'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.8:istack-commons-runtime-3.0.8.jar:4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef', 'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
'com.sun.xml.fastinfoset:FastInfoset:1.2.16:FastInfoset-1.2.16.jar:056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511', 'com.sun.xml.fastinfoset:FastInfoset:1.2.15:FastInfoset-1.2.15.jar:785861db11ca1bd0d1956682b974ad73eb19cd3e01a4b3fa82d62eca97210aec',
'com.thoughtworks.qdox:qdox:1.12.1:qdox-1.12.1.jar:21fba22f830e9268f07cf4ab2d99e8181abbdcb0cb91ee0228eb3cb918dcdd1d', 'commons-beanutils:commons-beanutils:1.9.4:commons-beanutils-1.9.4.jar:7d938c81789028045c08c065e94be75fc280527620d5bd62b519d5838532368a',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569', 'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-io:commons-io:2.4:commons-io-2.4.jar:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581', 'commons-collections:commons-collections:3.2.2:commons-collections-3.2.2.jar:eeeae917917144a68a741d4c0dff66aa5c5c5fd85593ff217bced3fc8ca783b8',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636', 'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'info.picocli:picocli:4.5.2:picocli-4.5.2.jar:b4395e9a67932616efd2245d984bf5fcd453c2c5049558c3ce959ac2af4d3fac', 'info.picocli:picocli:4.1.1:picocli-4.1.1.jar:9238b6af4ded57dcfedf6f7dbcf2bdf334dae2f118ea2a0bd6d05066e63ee6fc',
'it.unimi.dsi:fastutil:8.4.0:fastutil-8.4.0.jar:2ad2824a4a0a0eb836b52ee2fc84ba2134f44bce7bfa54015ae3f31c710a3071', 'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'jakarta.activation:jakarta.activation-api:1.2.1:jakarta.activation-api-1.2.1.jar:8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b', 'javax.activation:javax.activation-api:1.2.0:javax.activation-api-1.2.0.jar:43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393',
'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2:jakarta.xml.bind-api-2.3.2.jar:69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'jline:jline:2.14.6:jline-2.14.6.jar:97d1acaac82409be42e622d7a54d3ae9d08517e8aefdea3d2ba9791150c2f02d', 'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:5.6.0:jna-platform-5.6.0.jar:9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7',
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'net.sf.saxon:Saxon-HE:9.9.1-5:Saxon-HE-9.9.1-5.jar:4b39a9e30d5b634b5e91db5f0c6937f176d4517543ababc9b5e01830b8c56620',
'org.antlr:antlr4-runtime:4.7.2:antlr4-runtime-4.7.2.jar:4c518b87d4bdff8b44cd8cbc1af816e944b62a3fe5b80b781501cf1f4759bbc4',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.apache.ant:ant-antlr:1.10.9:ant-antlr-1.10.9.jar:7623dc9d0f20ea713290c6bf1a23f4c059447aef7ff9f5b2be75960f3f028d2e', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.ant:ant-junit:1.10.9:ant-junit-1.10.9.jar:960bdc8827954d62206ba42d0a68a7ee4476175ba47bb113e17e77cce7394630',
'org.apache.ant:ant-launcher:1.10.9:ant-launcher-1.10.9.jar:fcce891f57f3be72149ff96ac2a80574165b3e0839866b95d24528f3027d50c1',
'org.apache.ant:ant:1.10.9:ant-1.10.9.jar:0715478af585ea80a18985613ebecdc7922122d45b2c3c970ff9b352cddb75fc',
'org.apache.commons:commons-compress:1.20:commons-compress-1.20.jar:0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7', 'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd', 'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e', 'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89', 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9', 'org.briarproject:tor-android:0.3.5.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4', 'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
'org.codehaus.groovy:groovy-ant:3.0.7:groovy-ant-3.0.7.jar:6ed2ba82813d128f7050c24142e87b3dc2ad8b504786280eb03e81f0cf6a5793', 'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.codehaus.groovy:groovy-astbuilder:3.0.7:groovy-astbuilder-3.0.7.jar:b290451eb1583666e906c41f7d14747b4cc96363c99c478b244634fd5dfc9013',
'org.codehaus.groovy:groovy-cli-picocli:3.0.7:groovy-cli-picocli-3.0.7.jar:71b4bd11fb30a9c7b5618e22122c9c5141958fb27f4dcf0068b6f715088f6916',
'org.codehaus.groovy:groovy-console:3.0.7:groovy-console-3.0.7.jar:0541b358b6b8e5363215026736168fccfec1d91bac678d066fa77349eeeaa5dd',
'org.codehaus.groovy:groovy-datetime:3.0.7:groovy-datetime-3.0.7.jar:b9823d14b1a4f94236ae2f8a471701aab17e093e1b33402b91550b5c8dd88f04',
'org.codehaus.groovy:groovy-docgenerator:3.0.7:groovy-docgenerator-3.0.7.jar:bf53f7a11c9eb1e278e1b8ed2714c741bcf781235c803ad3ba1555f2614573f3',
'org.codehaus.groovy:groovy-groovydoc:3.0.7:groovy-groovydoc-3.0.7.jar:86b24dfc23c005066ab83927cdb54177f06c9531773f2e2d2ecc9a131f7c2677',
'org.codehaus.groovy:groovy-groovysh:3.0.7:groovy-groovysh-3.0.7.jar:5c40e78cbc09726aedd1c75fab112d245d665d6294870f9119e6cd3013ed14ab',
'org.codehaus.groovy:groovy-jmx:3.0.7:groovy-jmx-3.0.7.jar:0a89f3007884eb156751937d93382038b83d39c7c2f0ab156ebf251a7251f2ab',
'org.codehaus.groovy:groovy-json:3.0.7:groovy-json-3.0.7.jar:df1f0ee475e3fc93a6a0d17548294e160cca5de6d9d36817a7be1fbe650de03b',
'org.codehaus.groovy:groovy-jsr223:3.0.7:groovy-jsr223-3.0.7.jar:1dbd969595332416193baa660fbb45743d19696eaa25fe98e591a2739e13517e',
'org.codehaus.groovy:groovy-macro:3.0.7:groovy-macro-3.0.7.jar:c6cc06df526b39e2c359e2435f0071594c5a1c7babafaa6c184fdd8fa931531f',
'org.codehaus.groovy:groovy-nio:3.0.7:groovy-nio-3.0.7.jar:db54c577882b294cd8c975ec5451596441baf54781319c61627dca0e0c2361ef',
'org.codehaus.groovy:groovy-servlet:3.0.7:groovy-servlet-3.0.7.jar:5b6a909bf501c209adfb6205b9e740649609074455fd979bf9da4853e6ff9a39',
'org.codehaus.groovy:groovy-sql:3.0.7:groovy-sql-3.0.7.jar:252bb6c74e1a9f41756ad4fbd3b0d2eddc93bb61109961dd1952a37bf2d57a64',
'org.codehaus.groovy:groovy-swing:3.0.7:groovy-swing-3.0.7.jar:bd942032d9328d54c6679c49a41f6caa0d4a0039ebe598493b8a647730d98cff',
'org.codehaus.groovy:groovy-templates:3.0.7:groovy-templates-3.0.7.jar:f119e07f650ef186ae5a4b944f9e30915b14311bad47c94a6b32de8d4f69bc80',
'org.codehaus.groovy:groovy-test-junit5:3.0.7:groovy-test-junit5-3.0.7.jar:c16eeea07b8e396891e266d7ba9388b24ac804237ffdd9a792b0d08969bad014',
'org.codehaus.groovy:groovy-test:3.0.7:groovy-test-3.0.7.jar:f71afd7c25d43017f89ea47e6de6daec971d159047dae083c1513a8422d44b90',
'org.codehaus.groovy:groovy-testng:3.0.7:groovy-testng-3.0.7.jar:713d5f2231bbb5712aefd362151b9ffd884aeb7ef2e773315cc54259cbdd063d',
'org.codehaus.groovy:groovy-xml:3.0.7:groovy-xml-3.0.7.jar:8a62e7c9ddece3e82676c4bef2f2c100f459602cd1fb6a14e94187bf863e97ff',
'org.codehaus.groovy:groovy:3.0.7:groovy-3.0.7.jar:51d1777e8dd1f00e60ea56e00d8a354ff5aab1f00fc8464ae8d39d71867e401f',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.glassfish.jaxb:jaxb-runtime:2.3.2:jaxb-runtime-2.3.2.jar:e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b', 'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:txw2:2.3.2:txw2-2.3.2.jar:4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe', 'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jacoco:org.jacoco.agent:0.8.3:org.jacoco.agent-0.8.3.jar:522deb254ee16a04cc8341cc8f335f5cb7232982994d961b9cf3a0454709209f', 'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jacoco:org.jacoco.ant:0.8.3:org.jacoco.ant-0.8.3.jar:735844e1ae15f9b875b42a27ac5cb61cc26e106d9e839e5d1c6756709b424ce0', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jacoco:org.jacoco.core:0.8.3:org.jacoco.core-0.8.3.jar:0818437bc060a0c7cc798148f22b713702aae2771aba104444407697d578f1ea',
'org.jacoco:org.jacoco.report:0.8.3:org.jacoco.report-0.8.3.jar:aae08fa4ff043c807b8876cdb2d8705eb8449a55efce461baa6c09da245088c1',
'org.jetbrains.intellij.deps:trove4j:1.0.20181211:trove4j-1.0.20181211.jar:affb7c85a3c87bdcf69ff1dbb84de11f63dc931293934bc08cd7ab18de083601',
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145', 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262', 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656', 'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5', 'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09', 'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b', 'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd', 'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.junit.jupiter:junit-jupiter-api:5.7.0:junit-jupiter-api-5.7.0.jar:b03f78e0daeed2d77a0af9bcd662b4cdb9693f7ee72e01a539b508b84c63d182', 'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.junit.jupiter:junit-jupiter-engine:5.7.0:junit-jupiter-engine-5.7.0.jar:dfa26af94644ac2612dde6625852fcb550a0d21caa243257de54cba738ba87af',
'org.junit.platform:junit-platform-commons:1.7.0:junit-platform-commons-1.7.0.jar:5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea',
'org.junit.platform:junit-platform-engine:1.7.0:junit-platform-engine-1.7.0.jar:75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2',
'org.junit.platform:junit-platform-launcher:1.7.0:junit-platform-launcher-1.7.0.jar:fbdc748fde4c4279fe1d3c607447cb3b7ccd45d7338fc574f8a894ddf2d16818',
'org.jvnet.staxex:stax-ex:1.8.1:stax-ex-1.8.1.jar:20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.opentest4j:opentest4j:1.2.0:opentest4j-1.2.0.jar:58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474', 'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d', 'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c', 'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145', 'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf', 'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.testng:testng:7.3.0:testng-7.3.0.jar:63727488f9717d57f0d0a0fee5a1fc10a2be9cfcff2ec3a7187656d663c0774e',
'xerces:xercesImpl:2.12.0:xercesImpl-2.12.0.jar:b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0',
'xml-apis:xml-apis:1.4.01:xml-apis-1.4.01.jar:a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad',
] ]
} }

View File

@@ -3,17 +3,18 @@ sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'checkstyle'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
dependencies { dependencies {
implementation "com.google.dagger:dagger:$dagger_version" implementation "com.google.dagger:dagger:$dagger_version"
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.api;
public interface Cancellable {
void cancel();
}

View File

@@ -10,12 +10,4 @@ public interface FeatureFlags {
boolean shouldEnableProfilePictures(); boolean shouldEnableProfilePictures();
boolean shouldEnableDisappearingMessages(); boolean shouldEnableDisappearingMessages();
boolean shouldEnableMailbox();
boolean shouldEnablePrivateGroupsInCore();
boolean shouldEnableForumsInCore();
boolean shouldEnableBlogsInCore();
} }

View File

@@ -1,14 +1,8 @@
package org.briarproject.bramble.api; package org.briarproject.bramble.api;
import org.briarproject.bramble.util.StringUtils;
import java.util.ArrayList;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable;
public abstract class StringMap extends Hashtable<String, String> { public abstract class StringMap extends Hashtable<String, String> {
protected StringMap(Map<String, String> m) { protected StringMap(Map<String, String> m) {
@@ -58,31 +52,4 @@ public abstract class StringMap extends Hashtable<String, String> {
public void putLong(String key, long value) { public void putLong(String key, long value) {
put(key, String.valueOf(value)); put(key, String.valueOf(value));
} }
@Nullable
public int[] getIntArray(String key) {
String s = get(key);
if (s == null) return null;
// Handle empty string because "".split(",") returns {""}
if (s.length() == 0) return new int[0];
String[] intStrings = s.split(",");
int[] ints = new int[intStrings.length];
try {
for (int i = 0; i < ints.length; i++) {
ints[i] = Integer.parseInt(intStrings[i]);
}
} catch (NumberFormatException e) {
return null;
}
return ints;
}
public void putIntArray(String key, int[] value) {
List<String> intStrings = new ArrayList<>();
for (int integer : value) {
intStrings.add(String.valueOf(integer));
}
// Puts empty string if input array value is empty
put(key, StringUtils.join(intStrings, ","));
}
} }

View File

@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
public class UniqueId extends Bytes { public abstract class UniqueId extends Bytes {
/** /**
* The length of a unique identifier in bytes. * The length of a unique identifier in bytes.
*/ */
public static final int LENGTH = 32; public static final int LENGTH = 32;
public UniqueId(byte[] id) { protected UniqueId(byte[] id) {
super(id); super(id);
if (id.length != LENGTH) throw new IllegalArgumentException(); if (id.length != LENGTH) throw new IllegalArgumentException();
} }

View File

@@ -1,35 +0,0 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.lang.ref.WeakReference;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Provider;
/**
* A {@link Provider} that keeps a {@link WeakReference} to the last provided
* instance and provides the same instance again until the instance is garbage
* collected.
*/
@NotNullByDefault
public abstract class WeakSingletonProvider<T> implements Provider<T> {
private final Object lock = new Object();
@GuardedBy("lock")
private WeakReference<T> ref = new WeakReference<>(null);
@Override
public T get() {
synchronized (lock) {
T instance = ref.get();
if (instance == null) {
instance = createInstance();
ref = new WeakReference<>(instance);
}
return instance;
}
}
public abstract T createInstance();
}

View File

@@ -21,8 +21,9 @@ public interface CleanupHook {
* <p> * <p>
* The callee is not required to delete the messages, but the hook won't be * The callee is not required to delete the messages, but the hook won't be
* called again for these messages unless another cleanup timer is set (see * called again for these messages unless another cleanup timer is set (see
* {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)} * {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId,
* and {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)}). * long)} and {@link DatabaseComponent#startCleanupTimer(Transaction,
* MessageId)}).
*/ */
void deleteMessages(Transaction txn, GroupId g, void deleteMessages(Transaction txn, GroupId g,
Collection<MessageId> messageIds) throws DbException; Collection<MessageId> messageIds) throws DbException;

View File

@@ -18,8 +18,8 @@ import org.briarproject.bramble.api.sync.MessageId;
* {@link CleanupTimerStartedEvent CleanupTimerStartedEvents} broadcast by the * {@link CleanupTimerStartedEvent CleanupTimerStartedEvents} broadcast by the
* {@link DatabaseComponent}. * {@link DatabaseComponent}.
* <p> * <p>
* See {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)}, * See {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId,
* {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)}, * long)}, {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)},
* {@link DatabaseComponent#stopCleanupTimer(Transaction, MessageId)}. * {@link DatabaseComponent#stopCleanupTimer(Transaction, MessageId)}.
*/ */
@NotNullByDefault @NotNullByDefault

View File

@@ -9,8 +9,6 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -20,7 +18,6 @@ import org.briarproject.bramble.api.sync.MessageId;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
@NotNullByDefault @NotNullByDefault
@@ -126,25 +123,12 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap( Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;
/**
* Parse and validate the elements of a Mailbox update message.
*
* @return the parsed update message
* @throws FormatException if the message elements are invalid
*/
MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException;
List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException;
/** /**
* Retrieves the contact ID from the group metadata of the given contact * Retrieves the contact ID from the group metadata of the given contact
* group. * group.
*/ */
ContactId getContactId(Transaction txn, GroupId contactGroupId) ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException; throws DbException, FormatException;
/** /**
* Stores the given contact ID in the group metadata of the given contact * Stores the given contact ID in the group metadata of the given contact

View File

@@ -107,32 +107,6 @@ public interface ContactManager {
*/ */
String getHandshakeLink() throws DbException; String getHandshakeLink() throws DbException;
/**
* Returns the handshake link that needs to be sent to a contact we want
* to add.
*/
String getHandshakeLink(Transaction txn) throws DbException;
/**
* Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it.
*
* @param link The handshake link received from the pending contact
* @param alias The alias the user has given this pending contact
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
* @throws GeneralSecurityException If the pending contact's handshake
* public key is invalid
* @throws ContactExistsException If a contact with the same handshake
* public key already exists
* @throws PendingContactExistsException If a pending contact with the same
* handshake public key already exists
*/
PendingContact addPendingContact(Transaction txn, String link, String alias)
throws DbException, FormatException, GeneralSecurityException,
ContactExistsException, PendingContactExistsException;
/** /**
* Creates a {@link PendingContact} from the given handshake link and * Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it. * alias, adds it to the database and returns it.
@@ -166,24 +140,11 @@ public interface ContactManager {
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts() Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException; throws DbException;
/**
* Returns a list of {@link PendingContact PendingContacts} and their
* {@link PendingContactState states}.
*/
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(Transaction txn)
throws DbException;
/** /**
* Removes a {@link PendingContact}. * Removes a {@link PendingContact}.
*/ */
void removePendingContact(PendingContactId p) throws DbException; void removePendingContact(PendingContactId p) throws DbException;
/**
* Removes a {@link PendingContact}.
*/
void removePendingContact(Transaction txn, PendingContactId p)
throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
*/ */

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
@@ -16,4 +17,9 @@ public class PendingContactId extends UniqueId {
public PendingContactId(byte[] id) { public PendingContactId(byte[] id) {
super(id); super(id);
} }
@Override
public boolean equals(@Nullable Object o) {
return o instanceof PendingContactId && super.equals(o);
}
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.api.crypto; package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@@ -11,8 +10,6 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface CryptoComponent { public interface CryptoComponent {
UniqueId generateUniqueId();
SecretKey generateSecretKey(); SecretKey generateSecretKey();
SecureRandom getSecureRandom(); SecureRandom getSecureRandom();
@@ -173,13 +170,4 @@ public interface CryptoComponent {
* length. The line terminator is CRLF. * length. The line terminator is CRLF.
*/ */
String asciiArmour(byte[] b, int lineLength); String asciiArmour(byte[] b, int lineLength);
/**
* Encode the Onion given its public key. Specified here:
* https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
*
* @return the encoded onion, base32 chars
*/
String encodeOnion(byte[] publicKey);
} }

View File

@@ -33,18 +33,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/** /**
* Encapsulates the database implementation and exposes high-level operations * Encapsulates the database implementation and exposes high-level operations
* to other components. * to other components.
* <p>
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
* {@link #close()} methods, which must not be called concurrently, the
* database can be accessed from any thread. See {@link TransactionManager}
* for locking behaviour.
*/ */
@ThreadSafe
@NotNullByDefault @NotNullByDefault
public interface DatabaseComponent extends TransactionManager { public interface DatabaseComponent extends TransactionManager {
@@ -200,15 +193,26 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException; throws DbException;
/** /**
* Returns a batch of messages for the given contact, for transmission over * Returns a batch of messages for the given contact, with a total length
* a transport with the given maximum latency. The total length of the * less than or equal to the given length, for transmission over a
* messages, including record headers, will be no more than the given * transport with the given maximum latency. Returns null if there are no
* capacity. Returns null if there are no sendable messages that would fit * sendable messages that fit in the given length.
* in the given capacity.
*/ */
@Nullable @Nullable
Collection<Message> generateBatch(Transaction txn, ContactId c, Collection<Message> generateBatch(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/**
* Returns a batch of messages for the given contact containing the
* messages with the given IDs, for transmission over a transport with
* the given maximum latency.
* <p/>
* If any of the given messages are not in the database or are not visible
* to the contact, they are omitted from the batch without throwing an
* exception.
*/
Collection<Message> generateBatch(Transaction txn, ContactId c,
Collection<MessageId> ids, long maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
@@ -228,16 +232,15 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException; throws DbException;
/** /**
* Returns a batch of messages for the given contact, for transmission over * Returns a batch of messages for the given contact, with a total length
* a transport with the given maximum latency. Only messages that have been * less than or equal to the given length, for transmission over a
* requested by the contact are returned. The total length of the messages, * transport with the given maximum latency. Only messages that have been
* including record headers, will be no more than the given capacity. * requested by the contact are returned. Returns null if there are no
* Returns null if there are no sendable messages that have been requested * sendable messages that fit in the given length.
* by the contact and would fit in the given capacity.
*/ */
@Nullable @Nullable
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c, Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
@@ -341,30 +344,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g, Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
Metadata query) throws DbException; Metadata query) throws DbException;
/**
* Returns the IDs of some messages received from the given contact that
* need to be acknowledged, up to the given number of messages.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
int maxMessages) throws DbException;
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact over a transport with the given maximum latency. The total
* length of the messages including record headers will be no more than the
* given capacity.
* <p/>
* Unlike {@link #getUnackedMessagesToSend(Transaction, ContactId)} this
* method does not return messages that have already been sent unless they
* are due for retransmission.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToSend(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
* <p/> * <p/>
@@ -481,40 +460,17 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/**
* Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact.
*
* @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling
* {@link #setMessagesSent(Transaction, ContactId, Collection, long)}.
*/
@Nullable
Message getMessageToSend(Transaction txn, ContactId c, MessageId m,
long maxLatency, boolean markAsSent) throws DbException;
/** /**
* Returns the IDs of all messages that are eligible to be sent to the * Returns the IDs of all messages that are eligible to be sent to the
* given contact. * given contact, together with their raw lengths. This may include
* <p> * messages that have already been sent and are not yet due for
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, long)} * retransmission.
* this method may return messages that have already been sent and are
* not yet due for retransmission.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getUnackedMessagesToSend(Transaction txn, Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException; ContactId c) throws DbException;
/**
* Resets the transmission count, expiry time and max latency of all messages
* that are eligible to be sent to the given contact. This includes messages
* that have already been sent and are not yet due for retransmission.
*/
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
throws DbException;
/** /**
* Returns the total length, including headers, of all messages that are * Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages * eligible to be sent to the given contact. This may include messages
@@ -684,13 +640,6 @@ public interface DatabaseComponent extends TransactionManager {
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k) void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/**
* Records an ack for the given messages as having been sent to the given
* contact.
*/
void setAckSent(Transaction txn, ContactId c, Collection<MessageId> acked)
throws DbException;
/** /**
* Sets the cleanup timer duration for the given message. This does not * Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer. * start the message's cleanup timer.
@@ -737,13 +686,6 @@ public interface DatabaseComponent extends TransactionManager {
void setMessageState(Transaction txn, MessageId m, MessageState state) void setMessageState(Transaction txn, MessageId m, MessageState state)
throws DbException; throws DbException;
/**
* Records the given messages as having been sent to the given contact
* over a transport with the given maximum latency.
*/
void setMessagesSent(Transaction txn, ContactId c,
Collection<MessageId> sent, long maxLatency) throws DbException;
/** /**
* Adds dependencies for a message * Adds dependencies for a message
*/ */

View File

@@ -18,10 +18,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* submitted, tasks are not run concurrently, and submitting a task will never * submitted, tasks are not run concurrently, and submitting a task will never
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are * block. Tasks must not run indefinitely. Tasks submitted during shutdown are
* discarded. * discarded.
* <p>
* It is not mandatory to use this executor for database tasks. The database
* can be accessed from any thread, but this executor's guarantee that tasks
* are run in the order they're submitted may be useful in some cases.
*/ */
@Qualifier @Qualifier
@Target({FIELD, METHOD, PARAMETER}) @Target({FIELD, METHOD, PARAMETER})

View File

@@ -45,9 +45,6 @@ public class Transaction {
/** /**
* Attaches an event to be broadcast when the transaction has been * Attaches an event to be broadcast when the transaction has been
* committed. The event will be broadcast on the {@link EventExecutor}. * committed. The event will be broadcast on the {@link EventExecutor}.
* Events and {@link #attach(Runnable) tasks} are submitted to the
* {@link EventExecutor} in the order they were attached to the
* transaction.
*/ */
public void attach(Event e) { public void attach(Event e) {
if (actions == null) actions = new ArrayList<>(); if (actions == null) actions = new ArrayList<>();
@@ -57,9 +54,6 @@ public class Transaction {
/** /**
* Attaches a task to be executed when the transaction has been * Attaches a task to be executed when the transaction has been
* committed. The task will be run on the {@link EventExecutor}. * committed. The task will be run on the {@link EventExecutor}.
* {@link #attach(Event) Events} and tasks are submitted to the
* {@link EventExecutor} in the order they were attached to the
* transaction.
*/ */
public void attach(Runnable r) { public void attach(Runnable r) {
if (actions == null) actions = new ArrayList<>(); if (actions == null) actions = new ArrayList<>();

View File

@@ -1,95 +1,51 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* An interface for managing database transactions.
* <p>
* Read-only transactions may access the database concurrently. Read-write
* transactions access the database exclusively, so starting a read-only or
* read-write transaction will block until there are no read-write
* transactions in progress.
* <p>
* Failing to {@link #endTransaction(Transaction) end} a transaction will
* prevent other callers from accessing the database, so it is recommended to
* use the {@link #transaction(boolean, DbRunnable)},
* {@link #transactionWithResult(boolean, DbCallable)} and
* {@link #transactionWithNullableResult(boolean, NullableDbCallable)} methods
* where possible, which handle committing or aborting the transaction on the
* caller's behalf.
* <p>
* Transactions are not reentrant, i.e. it is not permitted to start a
* transaction on a thread that already has a transaction in progress.
*/
@ThreadSafe
@NotNullByDefault @NotNullByDefault
public interface TransactionManager { public interface TransactionManager {
/** /**
* Starts a new transaction and returns an object representing it. This * Starts a new transaction and returns an object representing it.
* method acquires the database lock, which is held until * <p/>
* {@link #endTransaction(Transaction)} is called. * This method acquires locks, so it must not be called while holding a
* lock.
* *
* @param readOnly True if the transaction will only be used for reading, * @param readOnly true if the transaction will only be used for reading.
* in which case the database lock can be shared with other read-only
* transactions.
*/ */
Transaction startTransaction(boolean readOnly) throws DbException; Transaction startTransaction(boolean readOnly) throws DbException;
/** /**
* Commits a transaction to the database. * Commits a transaction to the database.
* {@link #endTransaction(Transaction)} must be called to release the
* database lock.
*/ */
void commitTransaction(Transaction txn) throws DbException; void commitTransaction(Transaction txn) throws DbException;
/** /**
* Ends a transaction. If the transaction has not been committed by * Ends a transaction. If the transaction has not been committed,
* calling {@link #commitTransaction(Transaction)}, it is aborted and the * it will be aborted. If the transaction has been committed,
* database lock is released. * any events attached to the transaction are broadcast.
* <p> * The database lock will be released in either case.
* If the transaction has been committed, any
* {@link Transaction#attach events} attached to the transaction are
* broadcast and any {@link Transaction#attach(Runnable) tasks} attached
* to the transaction are submitted to the {@link EventExecutor}. The
* database lock is then released.
*/ */
void endTransaction(Transaction txn); void endTransaction(Transaction txn);
/** /**
* Runs the given task within a transaction. The database lock is held * Runs the given task within a transaction.
* while running the task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/ */
<E extends Exception> void transaction(boolean readOnly, <E extends Exception> void transaction(boolean readOnly,
DbRunnable<E> task) throws DbException, E; DbRunnable<E> task) throws DbException, E;
/** /**
* Runs the given task within a transaction and returns the result of the * Runs the given task within a transaction and returns the result of the
* task. The database lock is held while running the task. * task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/ */
<R, E extends Exception> R transactionWithResult(boolean readOnly, <R, E extends Exception> R transactionWithResult(boolean readOnly,
DbCallable<R, E> task) throws DbException, E; DbCallable<R, E> task) throws DbException, E;
/** /**
* Runs the given task within a transaction and returns the result of the * Runs the given task within a transaction and returns the result of the
* task, which may be null. The database lock is held while running the * task, which may be null.
* task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/ */
@Nullable @Nullable
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly, <R, E extends Exception> R transactionWithNullableResult(boolean readOnly,

View File

@@ -21,4 +21,9 @@ public class AuthorId extends UniqueId {
public AuthorId(byte[] id) { public AuthorId(byte[] id) {
super(id); super(id);
} }
@Override
public boolean equals(Object o) {
return o instanceof AuthorId && super.equals(o);
}
} }

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class InvalidMailboxIdException extends Exception {
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxAuthToken extends MailboxId {
public MailboxAuthToken(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxAuthToken} from the given string.
*
* @throws InvalidMailboxIdException if token is not valid.
*/
public static MailboxAuthToken fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxAuthToken(bytesFromString(token));
}
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
public interface MailboxConstants {
/**
* The maximum length of a file that can be uploaded to or downloaded from
* a mailbox.
*/
int MAX_FILE_BYTES = 1024 * 1024;
/**
* The maximum length of the plaintext payload of a file, such that the
* ciphertext is no more than {@link #MAX_FILE_BYTES}.
*/
int MAX_FILE_PAYLOAD_BYTES =
(MAX_FILE_BYTES - TAG_LENGTH - STREAM_HEADER_LENGTH)
/ MAX_FRAME_LENGTH * MAX_PAYLOAD_LENGTH;
/**
* The number of connection failures
* that indicate a problem with the mailbox.
*/
int PROBLEM_NUM_CONNECTION_FAILURES = 5;
/**
* The time in milliseconds since the last connection success
* that need to pass to indicates a problem with the mailbox.
*/
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxFileId extends MailboxId {
public MailboxFileId(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxFileId} from the given string.
*
* @throws IllegalArgumentException if token is not valid.
*/
public static MailboxFileId fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxFileId(bytesFromString(token));
}
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxFolderId extends MailboxId {
public MailboxFolderId(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxFolderId} from the given string.
*
* @throws IllegalArgumentException if token is not valid.
*/
public static MailboxFolderId fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxFolderId(bytesFromString(token));
}
}

View File

@@ -1,49 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import com.fasterxml.jackson.annotation.JsonValue;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Locale;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@ThreadSafe
@NotNullByDefault
public abstract class MailboxId extends UniqueId {
MailboxId(byte[] id) {
super(id);
}
/**
* Returns valid {@link MailboxId} bytes from the given string.
*
* @throws InvalidMailboxIdException if token is not valid.
*/
static byte[] bytesFromString(@Nullable String token)
throws InvalidMailboxIdException {
if (token == null || token.length() != 64) {
throw new InvalidMailboxIdException();
}
try {
return fromHexString(token);
} catch (IllegalArgumentException e) {
throw new InvalidMailboxIdException();
}
}
/**
* Returns the string representation expected by the mailbox API.
* Also used for serialization.
*/
@Override
@JsonValue
public String toString() {
return toHexString(getBytes()).toLowerCase(Locale.US);
}
}

View File

@@ -1,56 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import javax.annotation.Nullable;
public interface MailboxManager {
/**
* @return true if a mailbox is already paired.
*/
boolean isPaired(Transaction txn) throws DbException;
/**
* @return the current status of the mailbox.
*/
MailboxStatus getMailboxStatus(Transaction txn) throws DbException;
/**
* Returns the currently running pairing task,
* or null if no pairing task is running.
*/
@Nullable
MailboxPairingTask getCurrentPairingTask();
/**
* Starts and returns a pairing task. If a pairing task is already running,
* it will be returned and the argument will be ignored.
*
* @param qrCodePayload The ISO-8859-1 encoded bytes of the mailbox QR code.
*/
MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Can be used by the UI to test the mailbox connection.
*
* @return true (success) or false (error).
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
* {@link MailboxStatus}.
*/
boolean checkConnection();
/**
* Unpairs the owner's mailbox and tries to wipe it.
* As this makes a network call, it should be run on the {@link IoExecutor}.
*
* @return true if we could wipe the mailbox, false if we couldn't.
* It is advised to inform the user to wipe the mailbox themselves,
* if we failed to wipe it.
*/
@IoExecutor
boolean unPair() throws DbException;
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.api.mailbox;
public abstract class MailboxPairingState {
public static class QrCodeReceived extends MailboxPairingState {
}
public static class Pairing extends MailboxPairingState {
}
public static class Paired extends MailboxPairingState {
}
public static class InvalidQrCode extends MailboxPairingState {
}
public static class MailboxAlreadyPaired extends MailboxPairingState {
}
public static class ConnectionError extends MailboxPairingState {
}
public static class UnexpectedError extends MailboxPairingState {
}
}

View File

@@ -1,21 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface MailboxPairingTask extends Runnable {
/**
* Adds an observer to the task. The observer will be notified on the
* event thread of the current state of the task and any subsequent state
* changes.
*/
void addObserver(Consumer<MailboxPairingState> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<MailboxPairingState> observer);
}

View File

@@ -1,80 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxProperties {
private final String baseUrl;
private final MailboxAuthToken authToken;
private final boolean owner;
private final List<MailboxVersion> serverSupports;
@Nullable
private final MailboxFolderId inboxId; // Null for own mailbox
@Nullable
private final MailboxFolderId outboxId; // Null for own mailbox
/**
* Constructor for properties used by the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = true;
this.serverSupports = serverSupports;
this.inboxId = null;
this.outboxId = null;
}
/**
* Constructor for properties used by a contact of the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = false;
this.serverSupports = serverSupports;
this.inboxId = inboxId;
this.outboxId = outboxId;
}
public String getBaseUrl() {
return baseUrl;
}
public String getOnion() {
return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
}
public MailboxAuthToken getAuthToken() {
return authToken;
}
public boolean isOwner() {
return owner;
}
public List<MailboxVersion> getServerSupports() {
return serverSupports;
}
@Nullable
public MailboxFolderId getInboxId() {
return inboxId;
}
@Nullable
public MailboxFolderId getOutboxId() {
return outboxId;
}
}

View File

@@ -1,65 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxSettingsManager {
/**
* Registers a hook to be called when a mailbox has been paired or unpaired.
* This method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerMailboxHook(MailboxHook hook);
@Nullable
MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException;
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException;
void removeOwnMailboxProperties(Transaction txn) throws DbException;
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now)
throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException;
void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException;
@Nullable
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
interface MailboxHook {
/**
* Called when Briar is paired with a mailbox
*
* @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars)
*/
void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports)
throws DbException;
/**
* Called when the mailbox is unpaired
*
* @param txn A read-write transaction
*/
void mailboxUnpaired(Transaction txn) throws DbException;
}
}

View File

@@ -1,70 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
@Immutable
@NotNullByDefault
public class MailboxStatus {
private final long lastAttempt, lastSuccess;
private final int attemptsSinceSuccess;
public MailboxStatus(long lastAttempt, long lastSuccess,
int attemptsSinceSuccess) {
this.lastAttempt = lastAttempt;
this.lastSuccess = lastSuccess;
this.attemptsSinceSuccess = attemptsSinceSuccess;
}
/**
* Returns the time of the last attempt to connect to the mailbox, in
* milliseconds since the Unix epoch, or -1 if no attempt has been made.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* this method returns the time of the previous attempt, or -1 if the
* current attempt is the first.
*/
public long getTimeOfLastAttempt() {
return lastAttempt;
}
/**
* Returns the time of the last successful attempt to connect to the
* mailbox, in milliseconds since the Unix epoch, or -1 if no attempt has
* succeeded.
* <p>
* If the last attempt was successful then this method returns the same
* value as {@link #getTimeOfLastAttempt()}. If an attempt is in progress
* and has not yet succeeded or failed then this method returns the time
* of the previous successful connection, or -1 if no attempt has
* succeeded.
*/
public long getTimeOfLastSuccess() {
return lastSuccess;
}
/**
* Returns the number of attempts to connect to the mailbox that have
* failed since the last attempt succeeded, or the number of attempts that
* have been made, if no attempt has ever succeeded.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* it is not included in this count.
*/
public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess;
}
/**
* @return true if this status indicates a problem with the mailbox.
*/
public boolean hasProblem(long now) {
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
}
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxUpdate {
private final boolean hasMailbox;
private final List<MailboxVersion> clientSupports;
public MailboxUpdate(List<MailboxVersion> clientSupports) {
this(clientSupports, false);
}
MailboxUpdate(List<MailboxVersion> clientSupports, boolean hasMailbox) {
this.clientSupports = clientSupports;
this.hasMailbox = hasMailbox;
}
public List<MailboxVersion> getClientSupports() {
return clientSupports;
}
public boolean hasMailbox() {
return hasMailbox;
}
}

View File

@@ -1,72 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxUpdateManager {
/**
* The unique ID of the mailbox update (properties) client.
*/
ClientId CLIENT_ID =
new ClientId("org.briarproject.bramble.mailbox.properties");
/**
* The current major version of the mailbox update (properties) client.
*/
int MAJOR_VERSION = 2;
/**
* The current minor version of the mailbox update (properties) client.
*/
int MINOR_VERSION = 0;
/**
* The number of properties required for an update message with a mailbox.
*/
int PROP_COUNT = 4;
/**
* The required properties of an update message with a mailbox.
*/
String PROP_KEY_ONION = "onion";
String PROP_KEY_AUTHTOKEN = "authToken";
String PROP_KEY_INBOXID = "inboxId";
String PROP_KEY_OUTBOXID = "outboxId";
/**
* Length of the Onion property.
*/
int PROP_ONION_LENGTH = 56;
/**
* Message metadata key for the version number of a local or remote update,
* as a BDF long.
*/
String MSG_KEY_VERSION = "version";
/**
* Message metadata key for whether an update is local or remote, as a BDF
* boolean.
*/
String MSG_KEY_LOCAL = "local";
/**
* Key in the client's local group for storing the clientSupports list that
* was last sent out.
*/
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException;
@Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException;
}

View File

@@ -1,30 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxUpdateWithMailbox extends MailboxUpdate {
private final MailboxProperties properties;
public MailboxUpdateWithMailbox(List<MailboxVersion> clientSupports,
MailboxProperties properties) {
super(clientSupports, true);
if (properties.isOwner()) throw new IllegalArgumentException();
this.properties = properties;
}
public MailboxUpdateWithMailbox(MailboxUpdateWithMailbox o,
List<MailboxVersion> newClientSupports) {
this(newClientSupports, o.getMailboxProperties());
}
public MailboxProperties getMailboxProperties() {
return properties;
}
}

View File

@@ -1,44 +0,0 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxVersion implements Comparable<MailboxVersion> {
private final int major;
private final int minor;
public MailboxVersion(int major, int minor) {
this.major = major;
this.minor = minor;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
@Override
public boolean equals(Object o) {
if (o instanceof MailboxVersion) {
MailboxVersion v = (MailboxVersion) o;
return major == v.major && minor == v.minor;
}
return false;
}
@Override
public int compareTo(MailboxVersion v) {
int c = major - v.major;
if (c != 0) {
return c;
}
return minor - v.minor;
}
}

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast by {@link MailboxSettingsManager} when
* recording a connection failure for own Mailbox
* that has persistent for long enough for the mailbox owner to become active
* and fix the problem with the mailbox.
*/
@Immutable
@NotNullByDefault
public class MailboxProblemEvent extends Event {
}

View File

@@ -1,27 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast by {@link MailboxSettingsManager} when
* recording the connection status of own Mailbox.
*/
@Immutable
@NotNullByDefault
public class OwnMailboxConnectionStatusEvent extends Event {
private final MailboxStatus status;
public OwnMailboxConnectionStatusEvent(MailboxStatus status) {
this.status = status;
}
public MailboxStatus getStatus() {
return status;
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when {@link MailboxUpdate} are received
* from a contact.
*/
@Immutable
@NotNullByDefault
public class RemoteMailboxUpdateEvent extends Event {
private final ContactId contactId;
private final MailboxUpdate mailboxUpdate;
public RemoteMailboxUpdateEvent(ContactId contactId,
MailboxUpdate mailboxUpdate) {
this.contactId = contactId;
this.mailboxUpdate = mailboxUpdate;
}
public ContactId getContact() {
return contactId;
}
public MailboxUpdate getMailboxUpdate() {
return mailboxUpdate;
}
}

View File

@@ -1,27 +1,34 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.DAYS;
public interface TorConstants { public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor"); TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties // Transport properties
String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3"; String PROP_ONION_V3 = "onion3";
int DEFAULT_SOCKS_PORT = 59050; int DEFAULT_SOCKS_PORT = 59050;
int DEFAULT_CONTROL_PORT = 59051; int DEFAULT_CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5); int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120); int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
// Local settings (not shared with contacts) // Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2"; String PREF_TOR_NETWORK = "network2";
String PREF_TOR_PORT = "port"; String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData"; String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging"; String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
String HS_PRIVATE_KEY_V3 = "onionPrivKey3"; String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
String HS_V3_CREATED = "onionPrivKey3Created";
/**
* How long to publish a v3 hidden service before retiring the v2 service.
*/
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
// Values for PREF_TOR_NETWORK // Values for PREF_TOR_NETWORK
int PREF_TOR_NETWORK_AUTOMATIC = 0; int PREF_TOR_NETWORK_AUTOMATIC = 0;

View File

@@ -12,6 +12,4 @@ public interface RecordWriter {
void flush() throws IOException; void flush() throws IOException;
void close() throws IOException; void close() throws IOException;
long getBytesWritten();
} }

View File

@@ -22,11 +22,4 @@ public interface SettingsManager {
* namespace. * namespace.
*/ */
void mergeSettings(Settings s, String namespace) throws DbException; void mergeSettings(Settings s, String namespace) throws DbException;
/**
* Merges the given settings with any existing settings in the given
* namespace.
*/
void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException;
} }

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* An interface for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
* or acked at some later time.
*/
public interface DeferredSendHandler {
void onAckSent(Collection<MessageId> acked);
void onMessageSent(MessageId sent);
}

View File

@@ -20,4 +20,9 @@ public class GroupId extends UniqueId {
public GroupId(byte[] id) { public GroupId(byte[] id) {
super(id); super(id);
} }
@Override
public boolean equals(Object o) {
return o instanceof GroupId && super.equals(o);
}
} }

View File

@@ -27,4 +27,9 @@ public class MessageId extends UniqueId {
public MessageId(byte[] id) { public MessageId(byte[] id) {
super(id); super(id);
} }
@Override
public boolean equals(Object o) {
return o instanceof MessageId && super.equals(o);
}
} }

View File

@@ -20,6 +20,4 @@ public interface SyncRecordWriter {
void writePriority(Priority p) throws IOException; void writePriority(Priority p) throws IOException;
void flush() throws IOException; void flush() throws IOException;
long getBytesWritten();
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.api.system; package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -17,8 +16,6 @@ public interface TaskScheduler {
* <p> * <p>
* If the platform supports wake locks, a wake lock will be held while * If the platform supports wake locks, a wake lock will be held while
* submitting and running the task. * submitting and running the task.
*
* @return A {@link Cancellable} for cancelling the task.
*/ */
Cancellable schedule(Runnable task, Executor executor, long delay, Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit); TimeUnit unit);
@@ -30,11 +27,17 @@ public interface TaskScheduler {
* <p> * <p>
* If the platform supports wake locks, a wake lock will be held while * If the platform supports wake locks, a wake lock will be held while
* submitting and running the task. * submitting and running the task.
*
* @return A {@link Cancellable} for cancelling all future executions of
* the task.
*/ */
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit); long delay, long interval, TimeUnit unit);
interface Cancellable {
/**
* Cancels the task if it has not already started running. If the task
* is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long,
* TimeUnit) periodic}, all future executions of the task are cancelled.
*/
void cancel();
}
} }

View File

@@ -113,25 +113,9 @@ public interface KeyManager {
/** /**
* Looks up the given tag and returns a {@link StreamContext} for reading * Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was * from the corresponding stream, or null if an error occurs or the tag was
* unexpected. Marks the tag as recognised and updates the reordering * unexpected.
* window.
*/ */
@Nullable @Nullable
StreamContext getStreamContext(TransportId t, byte[] tag) StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException; throws DbException;
/**
* Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was
* unexpected. Only returns the StreamContext; does not mark the tag as
* recognised.
*/
@Nullable
StreamContext getStreamContextOnly(TransportId t, byte[] tag)
throws DbException;
/**
* Marks the tag as recognised and updates the reordering window.
*/
void markTagAsRecognised(TransportId t, byte[] tag) throws DbException;
} }

View File

@@ -26,7 +26,7 @@ public class NetworkUtils {
// Despite what the docs say, the return value can be null // Despite what the docs say, the return value can be null
//noinspection ConstantConditions //noinspection ConstantConditions
return ifaces == null ? emptyList() : list(ifaces); return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException | NullPointerException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return emptyList(); return emptyList();
} }

View File

@@ -1,17 +1,12 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.After; import org.junit.After;
public abstract class BrambleMockTestCase extends BrambleTestCase { public abstract class BrambleMockTestCase extends BrambleTestCase {
protected final Mockery context = new Mockery(); protected final Mockery context = new Mockery();
public BrambleMockTestCase() {
context.setThreadingPolicy(new Synchroniser());
}
@After @After
public void checkExpectations() { public void checkExpectations() {
context.assertIsSatisfied(); context.assertIsSatisfied();

View File

@@ -1,46 +1,17 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.junit.After;
import org.junit.Before;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.util.logging.Logger;
import javax.annotation.Nullable; import static org.junit.Assert.fail;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
public abstract class BrambleTestCase { public abstract class BrambleTestCase {
private static final Logger LOG =
getLogger(BrambleTestCase.class.getName());
@Nullable
protected volatile Throwable exceptionInBackgroundThread = null;
public BrambleTestCase() { public BrambleTestCase() {
// Ensure exceptions thrown on worker threads cause tests to fail // Ensure exceptions thrown on worker threads cause tests to fail
UncaughtExceptionHandler fail = (thread, throwable) -> { UncaughtExceptionHandler fail = (thread, throwable) -> {
LOG.log(WARNING, "Caught unhandled exception", throwable); throwable.printStackTrace();
exceptionInBackgroundThread = throwable; fail();
}; };
Thread.setDefaultUncaughtExceptionHandler(fail); Thread.setDefaultUncaughtExceptionHandler(fail);
} }
@Before
public void beforeBrambleTestCase() {
exceptionInBackgroundThread = null;
}
@After
public void afterBrambleTestCase() {
Throwable thrown = exceptionInBackgroundThread;
if (thrown != null) {
LOG.log(WARNING,
"Background thread has thrown an exception unexpectedly",
thrown);
throw new AssertionError(thrown);
}
}
} }

View File

@@ -12,20 +12,10 @@ import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey; import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey; import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.db.CommitAction;
import org.briarproject.bramble.api.db.EventAction;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
@@ -35,11 +25,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -49,8 +35,6 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
@@ -61,7 +45,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH; import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils { public class TestUtils {
@@ -226,37 +209,6 @@ public class TestUtils {
getAgreementPublicKey(), verified); getAgreementPublicKey(), verified);
} }
public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) {
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) {
return new MailboxProperties(baseUrl, authToken, serverSupports);
}
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId);
}
public static void writeBytes(File file, byte[] bytes)
throws IOException {
FileOutputStream outputStream = new FileOutputStream(file);
//noinspection TryFinallyCanBeTryWithResources
try {
outputStream.write(bytes);
} finally {
outputStream.close();
}
}
public static byte[] readBytes(File file) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
FileInputStream inputStream = new FileInputStream(file);
copyAndClose(inputStream, outputStream);
return outputStream.toByteArray();
}
public static double getMedian(Collection<? extends Number> samples) { public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size(); int size = samples.size();
if (size == 0) throw new IllegalArgumentException(); if (size == 0) throw new IllegalArgumentException();
@@ -291,48 +243,9 @@ public class TestUtils {
return Math.sqrt(getVariance(samples)); return Math.sqrt(getVariance(samples));
} }
public static boolean isOptionalTestEnabled(Class<?> testClass) { public static boolean isOptionalTestEnabled(Class testClass) {
String optionalTests = System.getenv("OPTIONAL_TESTS"); String optionalTests = System.getenv("OPTIONAL_TESTS");
return optionalTests != null && return optionalTests != null &&
asList(optionalTests.split(",")).contains(testClass.getName()); asList(optionalTests.split(",")).contains(testClass.getName());
} }
public static boolean mailboxUpdateEqual(@Nullable MailboxUpdate a,
@Nullable MailboxUpdate b) {
if (a == null || b == null) {
return a == b;
}
if (!a.hasMailbox() && !b.hasMailbox()) {
return a.getClientSupports().equals(b.getClientSupports());
} else if (a.hasMailbox() && b.hasMailbox()) {
MailboxUpdateWithMailbox am = (MailboxUpdateWithMailbox) a;
MailboxUpdateWithMailbox bm = (MailboxUpdateWithMailbox) b;
return am.getClientSupports().equals(bm.getClientSupports()) &&
mailboxPropertiesEqual(am.getMailboxProperties(),
bm.getMailboxProperties());
}
return false;
}
public static boolean mailboxPropertiesEqual(@Nullable MailboxProperties a,
@Nullable MailboxProperties b) {
if (a == null || b == null) {
return a == b;
}
return a.getOnion().equals(b.getOnion()) &&
a.getAuthToken().equals(b.getAuthToken()) &&
a.isOwner() == b.isOwner() &&
a.getServerSupports().equals(b.getServerSupports());
}
public static boolean hasEvent(Transaction txn,
Class<? extends Event> eventClass) {
for (CommitAction action : txn.getActions()) {
if (action instanceof EventAction) {
Event event = ((EventAction) action).getEvent();
if (eventClass.isInstance(event)) return true;
}
}
return false;
}
} }

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.test;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class ThreadExceptionTest extends BrambleTestCase {
@Test(expected = AssertionError.class)
public void testAssertionErrorMakesTestCaseFail() {
// This is what BrambleTestCase does, too:
fail();
}
@Test
public void testExceptionInThreadMakesTestCaseFail() {
Thread t = new Thread(() -> {
System.out.println("thread before exception");
throw new RuntimeException("boom");
});
t.start();
try {
t.join();
System.out.println("joined thread");
} catch (InterruptedException e) {
System.out.println("interrupted while joining thread");
fail();
}
assertNotNull(exceptionInBackgroundThread);
exceptionInBackgroundThread = null;
}
}

View File

@@ -1,16 +1,29 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'antlr:antlr:2.7.7:antlr-2.7.7.jar:88fbda4b912596b9f56e8e12e580cc954bacfb51776ecfddd3e18fc1cf56dc4c',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db', 'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79', 'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb', 'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.puppycrawl.tools:checkstyle:8.27:checkstyle-8.27.jar:26c81958a112ebdfc5d7b40507bbfc8f15f606fea1e55ef5675609ddb41a6053',
'commons-beanutils:commons-beanutils:1.9.4:commons-beanutils-1.9.4.jar:7d938c81789028045c08c065e94be75fc280527620d5bd62b519d5838532368a',
'commons-collections:commons-collections:3.2.2:commons-collections-3.2.2.jar:eeeae917917144a68a741d4c0dff66aa5c5c5fd85593ff217bced3fc8ca783b8',
'info.picocli:picocli:4.1.1:picocli-4.1.1.jar:9238b6af4ded57dcfedf6f7dbcf2bdf334dae2f118ea2a0bd6d05066e63ee6fc',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.sf.saxon:Saxon-HE:9.9.1-5:Saxon-HE-9.9.1-5.jar:4b39a9e30d5b634b5e91db5f0c6937f176d4517543ababc9b5e01830b8c56620',
'org.antlr:antlr4-runtime:4.7.2:antlr4-runtime-4.7.2.jar:4c518b87d4bdff8b44cd8cbc1af816e944b62a3fe5b80b781501cf1f4759bbc4',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249', 'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -3,6 +3,7 @@ sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'checkstyle'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
@@ -10,17 +11,12 @@ apply from: '../dagger.gradle'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') implementation project(path: ':bramble-api', configuration: 'default')
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70' implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
//noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.4' implementation 'org.briarproject:jtorctl:0.3'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -30,8 +26,7 @@ dependencies {
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
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.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -29,8 +28,6 @@ public interface BrambleCoreEagerSingletons {
void inject(LifecycleModule.EagerSingletons init); void inject(LifecycleModule.EagerSingletons init);
void inject(MailboxModule.EagerSingletons init);
void inject(PluginModule.EagerSingletons init); void inject(PluginModule.EagerSingletons init);
void inject(PropertiesModule.EagerSingletons init); void inject(PropertiesModule.EagerSingletons init);
@@ -54,7 +51,6 @@ public interface BrambleCoreEagerSingletons {
c.inject(new DatabaseExecutorModule.EagerSingletons()); c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new IdentityModule.EagerSingletons()); c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons()); c.inject(new LifecycleModule.EagerSingletons());
c.inject(new MailboxModule.EagerSingletons());
c.inject(new RendezvousModule.EagerSingletons()); c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule; import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule; import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.record.RecordModule;
@@ -44,7 +43,6 @@ import dagger.Module;
IoModule.class, IoModule.class,
KeyAgreementModule.class, KeyAgreementModule.class,
LifecycleModule.class, LifecycleModule.class,
MailboxModule.class,
PluginModule.class, PluginModule.class,
PropertiesModule.class, PropertiesModule.class,
RecordModule.class, RecordModule.class,

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.client; package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -23,12 +22,6 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -36,16 +29,13 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.Base32;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -56,12 +46,6 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_ONION_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@@ -415,69 +399,6 @@ class ClientHelperImpl implements ClientHelper {
return tpMap; return tpMap;
} }
@Override
public MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException {
List<MailboxVersion> clientSupportsList =
parseMailboxVersionList(clientSupports);
List<MailboxVersion> serverSupportsList =
parseMailboxVersionList(serverSupports);
// We must always learn what Mailbox API version(s) the client supports
if (clientSupports.isEmpty()) {
throw new FormatException();
}
if (properties.isEmpty()) {
// No mailbox -- cannot claim to support any API versions!
if (!serverSupports.isEmpty()) {
throw new FormatException();
}
return new MailboxUpdate(clientSupportsList);
}
// Mailbox must be accompanied by the Mailbox API version(s) it supports
if (serverSupports.isEmpty()) {
throw new FormatException();
}
// Accepting more props than we need, for forward compatibility
if (properties.size() < PROP_COUNT) {
throw new FormatException();
}
String onion = properties.getString(PROP_KEY_ONION);
checkLength(onion, PROP_ONION_LENGTH);
try {
Base32.decode(onion, true);
} catch (IllegalArgumentException e) {
throw new FormatException();
}
byte[] authToken = properties.getRaw(PROP_KEY_AUTHTOKEN);
checkLength(authToken, UniqueId.LENGTH);
byte[] inboxId = properties.getRaw(PROP_KEY_INBOXID);
checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH);
String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props);
}
@Override
public List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException {
List<MailboxVersion> list = new ArrayList<>();
for (int i = 0; i < bdfList.size(); i++) {
BdfList element = bdfList.getList(i);
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
}
return list;
}
@Override @Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId) public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException { throws DbException {

View File

@@ -121,40 +121,28 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override @Override
public String getHandshakeLink() throws DbException { public String getHandshakeLink() throws DbException {
return db.transactionWithResult(true, this::getHandshakeLink); KeyPair keyPair = db.transactionWithResult(true,
} identityManager::getHandshakeKeys);
@Override
public String getHandshakeLink(Transaction txn) throws DbException {
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
return pendingContactFactory.createHandshakeLink(keyPair.getPublic()); return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
} }
@Override
public PendingContact addPendingContact(Transaction txn, String link,
String alias)
throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
return p;
}
@Override @Override
public PendingContact addPendingContact(String link, String alias) public PendingContact addPendingContact(String link, String alias)
throws DbException, FormatException, GeneralSecurityException { throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
PendingContact p = addPendingContact(txn, link, alias); AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
db.commitTransaction(txn); db.commitTransaction(txn);
return p;
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
return p;
} }
@Override @Override
@@ -166,14 +154,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override @Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts() public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException { throws DbException {
return db.transactionWithResult(true, this::getPendingContacts); Collection<PendingContact> pendingContacts =
} db.transactionWithResult(true, db::getPendingContacts);
@Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(
Transaction txn)
throws DbException {
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
List<Pair<PendingContact, PendingContactState>> pairs = List<Pair<PendingContact, PendingContactState>> pairs =
new ArrayList<>(pendingContacts.size()); new ArrayList<>(pendingContacts.size());
for (PendingContact p : pendingContacts) { for (PendingContact p : pendingContacts) {
@@ -186,13 +168,7 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override @Override
public void removePendingContact(PendingContactId p) throws DbException { public void removePendingContact(PendingContactId p) throws DbException {
db.transaction(false, txn -> removePendingContact(txn, p)); db.transaction(false, txn -> db.removePendingContact(txn, p));
}
@Override
public void removePendingContact(Transaction txn, PendingContactId p)
throws DbException {
db.removePendingContact(txn, p);
states.remove(p); states.remove(p);
} }

View File

@@ -7,8 +7,6 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.Blake2bDigest; import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey; import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -23,13 +21,11 @@ import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey; import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.Base32;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
@@ -42,7 +38,6 @@ import javax.inject.Inject;
import static java.lang.System.arraycopy; import static java.lang.System.arraycopy;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
@@ -56,15 +51,13 @@ import static org.briarproject.bramble.util.LogUtils.now;
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG = private static final Logger LOG =
getLogger(CryptoComponentImpl.class.getName()); Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int SIGNATURE_KEY_PAIR_BITS = 256; private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final byte PBKDF_FORMAT_SCRYPT = 0; private static final byte PBKDF_FORMAT_SCRYPT = 0;
private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1; private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1;
private static final byte ONION_HS_PROTOCOL_VERSION = 3;
private static final int ONION_CHECKSUM_BYTES = 2;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf; private final PasswordBasedKdf passwordBasedKdf;
@@ -130,13 +123,6 @@ class CryptoComponentImpl implements CryptoComponent {
} }
} }
@Override
public UniqueId generateUniqueId() {
byte[] b = new byte[UniqueId.LENGTH];
secureRandom.nextBytes(b);
return new UniqueId(b);
}
@Override @Override
public SecretKey generateSecretKey() { public SecretKey generateSecretKey() {
byte[] b = new byte[SecretKey.LENGTH]; byte[] b = new byte[SecretKey.LENGTH];
@@ -456,21 +442,4 @@ class CryptoComponentImpl implements CryptoComponent {
public String asciiArmour(byte[] b, int lineLength) { public String asciiArmour(byte[] b, int lineLength) {
return AsciiArmour.wrap(b, lineLength); return AsciiArmour.wrap(b, lineLength);
} }
@Override
public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION);
byte[] checksum = new byte[digest.getDigestSize()];
digest.doFinal(checksum, 0);
byte[] address = new byte[publicKey.length + ONION_CHECKSUM_BYTES + 1];
arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase();
}
} }

View File

@@ -406,12 +406,6 @@ interface Database<T> {
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query) Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
throws DbException; throws DbException;
/**
* Returns the length of the given message in bytes, including the
* message header.
*/
int getMessageLength(T txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for all delivered messages in the given group. * Returns the metadata for all delivered messages in the given group.
* <p/> * <p/>
@@ -502,8 +496,7 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact. The total length of the messages including record headers * given contact, up to the given total length.
* will be no more than the given capacity.
* <p/> * <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method * Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are * does not return messages that have already been sent unless they are
@@ -511,20 +504,20 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity, Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
long maxLatency) throws DbException; long maxLatency) throws DbException;
/** /**
* Returns the IDs of all messages that are eligible to be sent to the * Returns the IDs of all messages that are eligible to be sent to the
* given contact. * given contact, together with their raw lengths.
* <p/> * <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, long, long)} this * Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
* method may return messages that have already been sent and are not yet * method may return messages that have already been sent and are not yet
* due for retransmission. * due for retransmission.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c) Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException; throws DbException;
/** /**
@@ -605,14 +598,13 @@ interface Database<T> {
/** /**
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact and have been requested by the contact. The total length * given contact and have been requested by the contact, up to the given
* of the messages including record headers will be no more than the given * total length.
* capacity.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c, Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
long capacity, long maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/** /**
* Returns all settings in the given namespace. * Returns all settings in the given namespace.
@@ -765,14 +757,6 @@ interface Database<T> {
*/ */
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException; void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Resets the transmission count, expiry time and max latency of all
* messages that are eligible to be sent to the given contact. This includes
* messages that have already been sent and are not yet due for
* retransmission.
*/
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
/** /**
* Sets the cleanup timer duration for the given message. This does not * Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer. * start the message's cleanup timer.
@@ -857,13 +841,11 @@ interface Database<T> {
void stopCleanupTimer(T txn, MessageId m) throws DbException; void stopCleanupTimer(T txn, MessageId m) throws DbException;
/** /**
* Updates the transmission count, expiry time and max latency of the given * Updates the transmission count, expiry time and estimated time of arrival
* message with respect to the given contact. * of the given message with respect to the given contact, using the latency
* * of the transport over which it was sent.
* @param maxLatency latency of the transport over which the message was
* sent.
*/ */
void updateRetransmissionData(T txn, ContactId c, MessageId m, void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m,
long maxLatency) throws DbException; long maxLatency) throws DbException;
/** /**

View File

@@ -75,6 +75,7 @@ import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -86,7 +87,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -424,27 +424,53 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable @Nullable
@Override @Override
public Collection<Message> generateBatch(Transaction transaction, public Collection<Message> generateBatch(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, c, capacity, maxLatency); db.getMessagesToSend(txn, c, maxLength, maxLatency);
if (ids.isEmpty()) return null;
long totalLength = 0; long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
Message message = db.getMessage(txn, m); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength(); totalLength += message.getRawLength();
messages.add(message); messages.add(message);
db.updateRetransmissionData(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages; return messages;
} }
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, Collection<MessageId> ids, long maxLatency)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
List<MessageId> sentIds = new ArrayList<>(ids.size());
for (MessageId m : ids) {
if (db.containsVisibleMessage(txn, c, m)) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
sentIds.add(m);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
}
if (messages.isEmpty()) return messages;
db.lowerRequestedFlag(txn, c, sentIds);
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
return messages;
}
@Nullable @Nullable
@Override @Override
public Offer generateOffer(Transaction transaction, ContactId c, public Offer generateOffer(Transaction transaction, ContactId c,
@@ -457,7 +483,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getMessagesToOffer(txn, c, maxMessages, maxLatency); db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
for (MessageId m : ids) for (MessageId m : ids)
db.updateRetransmissionData(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
return new Offer(ids); return new Offer(ids);
} }
@@ -479,22 +505,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable @Nullable
@Override @Override
public Collection<Message> generateRequestedBatch(Transaction transaction, public Collection<Message> generateRequestedBatch(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency); db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
if (ids.isEmpty()) return null;
long totalLength = 0; long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
Message message = db.getMessage(txn, m); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength(); totalLength += message.getRawLength();
messages.add(message); messages.add(message);
db.updateRetransmissionData(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages; return messages;
@@ -609,24 +635,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageIds(txn, g, query); return db.getMessageIds(txn, g, query);
} }
@Override
public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, maxMessages);
}
@Override
public Collection<MessageId> getMessagesToSend(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToSend(txn, c, capacity, maxLatency);
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction) public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException { throws DbException {
@@ -732,44 +740,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status; return status;
} }
@Nullable
@Override @Override
public Message getMessageToSend(Transaction transaction, ContactId c, public Map<MessageId, Integer> getUnackedMessagesToSend(
MessageId m, long maxLatency, boolean markAsSent) Transaction transaction,
throws DbException { ContactId c) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
if (!db.containsVisibleMessage(txn, c, m)) return null;
Message message = db.getMessage(txn, m);
if (markAsSent) {
db.updateRetransmissionData(txn, c, m, maxLatency);
db.lowerRequestedFlag(txn, c, singletonList(m));
transaction.attach(new MessagesSentEvent(c, singletonList(m),
message.getRawLength()));
}
return message;
}
@Override
public Collection<MessageId> getUnackedMessagesToSend(
Transaction transaction, ContactId c) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.getUnackedMessagesToSend(txn, c); return db.getUnackedMessagesToSend(txn, c);
} }
@Override
public void resetUnackedMessagesToSend(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.resetUnackedMessagesToSend(txn, c);
}
@Override @Override
public long getUnackedMessageBytesToSend(Transaction transaction, public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException { ContactId c) throws DbException {
@@ -1080,20 +1060,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeTransportKeys(txn, t, k); db.removeTransportKeys(txn, t, k);
} }
@Override
public void setAckSent(Transaction transaction, ContactId c,
Collection<MessageId> acked) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
}
@Override @Override
public void setCleanupTimerDuration(Transaction transaction, MessageId m, public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException { long duration) throws DbException {
@@ -1140,7 +1106,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED); if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g); else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED); else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c); List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }
@@ -1188,28 +1154,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageStateChangedEvent(m, false, state)); transaction.attach(new MessageStateChangedEvent(m, false, state));
} }
@Override
public void setMessagesSent(Transaction transaction, ContactId c,
Collection<MessageId> sent, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<MessageId> visible = new ArrayList<>(sent.size());
for (MessageId m : sent) {
if (db.containsVisibleMessage(txn, c, m)) {
visible.add(m);
totalLength += db.getMessageLength(txn, m);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
}
db.lowerRequestedFlag(txn, c, visible);
if (!visible.isEmpty()) {
transaction.attach(new MessagesSentEvent(c, visible, totalLength));
}
}
@Override @Override
public void addMessageDependencies(Transaction transaction, public void addMessageDependencies(Transaction transaction,
Message dependent, Collection<MessageId> dependencies) Message dependent, Collection<MessageId> dependencies)

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import static java.util.concurrent.TimeUnit.DAYS;
interface DatabaseConstants { interface DatabaseConstants {
/** /**
@@ -23,6 +25,19 @@ interface DatabaseConstants {
*/ */
String SCHEMA_VERSION_KEY = "schemaVersion"; String SCHEMA_VERSION_KEY = "schemaVersion";
/**
* The {@link Settings} key under which the time of the last database
* compaction is stored.
*/
String LAST_COMPACTED_KEY = "lastCompacted";
/**
* The maximum time between database compactions in milliseconds. When the
* database is opened it will be compacted if more than this amount of time
* has passed since the last compaction.
*/
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
/** /**
* The {@link Settings} key under which the flag is stored indicating * The {@link Settings} key under which the flag is stored indicating
* whether the database is marked as dirty. * whether the database is marked as dirty.

View File

@@ -85,17 +85,12 @@ class H2Database extends JdbcDatabase {
public void close() throws DbException { public void close() throws DbException {
// H2 will close the database when the last connection closes // H2 will close the database when the last connection closes
Connection c = null; Connection c = null;
Statement s = null;
try { try {
c = createConnection(); c = createConnection();
closeAllConnections(); super.closeAllConnections();
setDirty(c, false); setDirty(c, false);
s = c.createStatement();
s.execute("SHUTDOWN COMPACT");
s.close();
c.close(); c.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING); tryToClose(c, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }

View File

@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
Connection c = null; Connection c = null;
Statement s = null; Statement s = null;
try { try {
closeAllConnections(); super.closeAllConnections();
c = createConnection(); c = createConnection();
setDirty(c, false); setDirty(c, false);
s = c.createStatement(); s = c.createStatement();
s.executeQuery("SHUTDOWN COMPACT"); s.executeQuery("SHUTDOWN");
s.close(); s.close();
c.close(); c.close();
} catch (SQLException e) { } catch (SQLException e) {
@@ -106,7 +106,7 @@ class HyperSqlDatabase extends JdbcDatabase {
Connection c = null; Connection c = null;
Statement s = null; Statement s = null;
try { try {
closeAllConnections(); super.closeAllConnections();
c = createConnection(); c = createConnection();
s = c.createStatement(); s = c.createStatement();
s.executeQuery("SHUTDOWN COMPACT"); s.executeQuery("SHUTDOWN COMPACT");

View File

@@ -51,6 +51,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -69,14 +70,12 @@ import static java.sql.Types.BOOLEAN;
import static java.sql.Types.INTEGER; import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR; import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
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 java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE; import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED; import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -86,6 +85,8 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY; import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY; import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
@@ -101,12 +102,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
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 = 50; static final int CODE_SCHEMA_VERSION = 49;
/**
* The maximum number of idle connections to keep open.
*/
private static final int MAX_CONNECTION_POOL_SIZE = 1;
// Time period offsets for incoming transport keys // Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -256,7 +252,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " requested BOOLEAN NOT NULL," + " requested BOOLEAN NOT NULL,"
+ " expiry BIGINT NOT NULL," + " expiry BIGINT NOT NULL,"
+ " txCount INT NOT NULL," + " txCount INT NOT NULL,"
+ " maxLatency BIGINT," // Null if latency was reset + " eta BIGINT NOT NULL,"
+ " PRIMARY KEY (messageId, contactId)," + " PRIMARY KEY (messageId, contactId),"
+ " FOREIGN KEY (messageId)" + " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)" + " REFERENCES messages (messageId)"
@@ -369,7 +365,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private final Condition connectionsChanged = connectionsLock.newCondition(); private final Condition connectionsChanged = connectionsLock.newCondition();
@GuardedBy("connectionsLock") @GuardedBy("connectionsLock")
private final LinkedList<Connection> connectionPool = new LinkedList<>(); private final LinkedList<Connection> connections = new LinkedList<>();
@GuardedBy("connectionsLock") @GuardedBy("connectionsLock")
private int openConnections = 0; private int openConnections = 0;
@@ -382,7 +378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
throws DbException, SQLException; throws DbException, SQLException;
// Used exclusively during open to compact the database after schema // Used exclusively during open to compact the database after schema
// migrations or if the database was not shut down cleanly // migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
// elapsed
protected abstract void compactAndClose() throws DbException; protected abstract void compactAndClose() throws DbException;
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory, JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
@@ -408,8 +405,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (reopen) { if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE); Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s); wasDirtyOnInitialisation = isDirty(s);
boolean migrated = migrateSchema(txn, s, listener); compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
compact = wasDirtyOnInitialisation || migrated;
} else { } else {
wasDirtyOnInitialisation = false; wasDirtyOnInitialisation = false;
createTables(txn); createTables(txn);
@@ -433,11 +429,16 @@ abstract class JdbcDatabase implements Database<Connection> {
compactAndClose(); compactAndClose();
logDuration(LOG, "Compacting database", start); logDuration(LOG, "Compacting database", start);
// Allow the next transaction to reopen the DB // Allow the next transaction to reopen the DB
connectionsLock.lock(); synchronized (connectionsLock) {
try {
closed = false; closed = false;
} finally { }
connectionsLock.unlock(); txn = startTransaction();
try {
storeLastCompacted(txn);
commitTransaction(txn);
} catch (DbException e) {
abortTransaction(txn);
throw e;
} }
} }
} }
@@ -498,11 +499,18 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration45_46(), new Migration45_46(),
new Migration46_47(dbTypes), new Migration46_47(dbTypes),
new Migration47_48(), new Migration47_48(),
new Migration48_49(), new Migration48_49()
new Migration49_50()
); );
} }
private boolean isCompactionDue(Settings s) {
long lastCompacted = s.getLong(LAST_COMPACTED_KEY, 0);
long elapsed = clock.currentTimeMillis() - lastCompacted;
if (LOG.isLoggable(INFO))
LOG.info(elapsed + " ms since last compaction");
return elapsed > MAX_COMPACTION_INTERVAL_MS;
}
private void storeSchemaVersion(Connection txn, int version) private void storeSchemaVersion(Connection txn, int version)
throws DbException { throws DbException {
Settings s = new Settings(); Settings s = new Settings();
@@ -510,6 +518,12 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE); mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
} }
private void storeLastCompacted(Connection txn) throws DbException {
Settings s = new Settings();
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private boolean isDirty(Settings s) { private boolean isDirty(Settings s) {
return s.getBoolean(DIRTY_KEY, false); return s.getBoolean(DIRTY_KEY, false);
} }
@@ -523,6 +537,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private void initialiseSettings(Connection txn) throws DbException { private void initialiseSettings(Connection txn) throws DbException {
Settings s = new Settings(); Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION); s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE); mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
} }
@@ -577,8 +592,7 @@ abstract class JdbcDatabase implements Database<Connection> {
connectionsLock.lock(); connectionsLock.lock();
try { try {
if (closed) throw new DbClosedException(); if (closed) throw new DbClosedException();
txn = connectionPool.poll(); txn = connections.poll();
logConnectionCounts();
} finally { } finally {
connectionsLock.unlock(); connectionsLock.unlock();
} }
@@ -589,14 +603,7 @@ abstract class JdbcDatabase implements Database<Connection> {
txn.setAutoCommit(false); txn.setAutoCommit(false);
connectionsLock.lock(); connectionsLock.lock();
try { try {
// The DB may have been closed since the check above
if (closed) {
tryToClose(txn, LOG, WARNING);
throw new DbClosedException();
}
openConnections++; openConnections++;
logConnectionCounts();
connectionsChanged.signalAll();
} finally { } finally {
connectionsLock.unlock(); connectionsLock.unlock();
} }
@@ -607,91 +614,67 @@ abstract class JdbcDatabase implements Database<Connection> {
return txn; return txn;
} }
@GuardedBy("connectionsLock")
private void logConnectionCounts() {
if (LOG.isLoggable(FINE)) {
LOG.fine(openConnections + " connections open, "
+ connectionPool.size() + " in pool");
}
}
@Override @Override
public void abortTransaction(Connection txn) { public void abortTransaction(Connection txn) {
// The transaction may have been aborted due to an earlier exception,
// so close the connection rather than returning it to the pool
try { try {
txn.rollback(); txn.rollback();
connectionsLock.lock();
try {
connections.add(txn);
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
} catch (SQLException e) { } catch (SQLException e) {
// Try to close the connection
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} tryToClose(txn, LOG, WARNING);
closeConnection(txn); // Whatever happens, allow the database to close
} connectionsLock.lock();
try {
private void closeConnection(Connection txn) { openConnections--;
tryToClose(txn, LOG, WARNING); connectionsChanged.signalAll();
connectionsLock.lock(); } finally {
try { connectionsLock.unlock();
openConnections--; }
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
} }
} }
@Override @Override
public void commitTransaction(Connection txn) throws DbException { public void commitTransaction(Connection txn) throws DbException {
// If the transaction commits successfully then return the connection
// to the pool, otherwise close it
try { try {
txn.commit(); txn.commit();
returnConnectionToPool(txn);
} catch (SQLException e) { } catch (SQLException e) {
logException(LOG, WARNING, e);
closeConnection(txn);
throw new DbException(e); throw new DbException(e);
} }
}
private void returnConnectionToPool(Connection txn) {
boolean shouldClose;
connectionsLock.lock(); connectionsLock.lock();
try { try {
shouldClose = connectionPool.size() >= MAX_CONNECTION_POOL_SIZE; connections.add(txn);
if (shouldClose) openConnections--;
else connectionPool.add(txn);
logConnectionCounts();
connectionsChanged.signalAll(); connectionsChanged.signalAll();
} finally { } finally {
connectionsLock.unlock(); connectionsLock.unlock();
} }
if (shouldClose) tryToClose(txn, LOG, WARNING);
} }
void closeAllConnections() { void closeAllConnections() throws SQLException {
boolean interrupted = false; boolean interrupted = false;
connectionsLock.lock(); connectionsLock.lock();
try { try {
closed = true; closed = true;
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING); for (Connection c : connections) c.close();
openConnections -= connectionPool.size(); openConnections -= connections.size();
connectionPool.clear(); connections.clear();
while (openConnections > 0) { while (openConnections > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Waiting for " + openConnections
+ " connections to be closed");
}
try { try {
connectionsChanged.await(); connectionsChanged.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warning("Interrupted while closing connections"); LOG.warning("Interrupted while closing connections");
interrupted = true; interrupted = true;
} }
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING); for (Connection c : connections) c.close();
openConnections -= connectionPool.size(); openConnections -= connections.size();
connectionPool.clear(); connections.clear();
} }
LOG.info("All connections closed");
} finally { } finally {
connectionsLock.unlock(); connectionsLock.unlock();
} }
@@ -934,10 +917,9 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
String sql = "INSERT INTO statuses (messageId, contactId, groupId," String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
+ " timestamp, length, state, groupShared, messageShared," + " timestamp, length, state, groupShared, messageShared,"
+ " deleted, ack, seen, requested, expiry, txCount," + " deleted, ack, seen, requested, expiry, txCount, eta)"
+ " maxLatency)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0," + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
+ " NULL)"; + " 0)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
@@ -1171,17 +1153,17 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
} else { } else {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency;
sql = "SELECT NULL FROM statuses" sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?" + " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE" + " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL" + " AND (expiry <= ? OR eta > ?)";
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
ps.setLong(4, maxLatency); ps.setLong(4, eta);
} }
rs = ps.executeQuery(); rs = ps.executeQuery();
boolean messagesToSend = rs.next(); boolean messagesToSend = rs.next();
@@ -1917,31 +1899,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public int getMessageLength(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length from messages"
+ " WHERE messageId = ? AND state = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
int length = rs.getInt(1);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return length;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Map<MessageId, Metadata> getMessageMetadata(Connection txn, public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
GroupId g) throws DbException { GroupId g) throws DbException {
@@ -2234,6 +2191,7 @@ abstract class JdbcDatabase implements Database<Connection> {
public Collection<MessageId> getMessagesToOffer(Connection txn, public Collection<MessageId> getMessagesToOffer(Connection txn,
ContactId c, int maxMessages, long maxLatency) throws DbException { ContactId c, int maxMessages, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2242,14 +2200,13 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE" + " AND deleted = FALSE"
+ " AND seen = FALSE AND requested = FALSE" + " AND seen = FALSE AND requested = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL" + " AND (expiry <= ? OR eta > ?)"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp LIMIT ?"; + " ORDER BY timestamp LIMIT ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
ps.setLong(4, maxLatency); ps.setLong(4, eta);
ps.setInt(5, maxMessages); ps.setInt(5, maxMessages);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
@@ -2290,9 +2247,10 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<MessageId> getMessagesToSend(Connection txn, public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
ContactId c, long capacity, long maxLatency) throws DbException { int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2301,21 +2259,21 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE" + " AND deleted = FALSE"
+ " AND seen = FALSE" + " AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL" + " AND (expiry <= ? OR eta > ?)"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp"; + " ORDER BY timestamp";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
ps.setLong(4, maxLatency); ps.setLong(4, eta);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) { while (rs.next()) {
int length = rs.getInt(1); int length = rs.getInt(1);
if (capacity < RECORD_HEADER_BYTES + length) break; if (total + length > maxLength) break;
ids.add(new MessageId(rs.getBytes(2))); ids.add(new MessageId(rs.getBytes(2)));
capacity -= RECORD_HEADER_BYTES + length; total += length;
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2328,12 +2286,12 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<MessageId> getUnackedMessagesToSend(Connection txn, public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException { ContactId c) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId FROM statuses" String sql = "SELECT length, messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?" + " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE" + " AND deleted = FALSE AND seen = FALSE"
@@ -2342,11 +2300,15 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); Map<MessageId, Integer> results = new LinkedHashMap<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) {
int length = rs.getInt(1);
MessageId id = new MessageId(rs.getBytes(2));
results.put(id, length);
}
rs.close(); rs.close();
ps.close(); ps.close();
return ids; return results;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -2459,7 +2421,6 @@ abstract class JdbcDatabase implements Database<Connection> {
MessageId m = new MessageId(rs.getBytes(1)); MessageId m = new MessageId(rs.getBytes(1));
GroupId g = new GroupId(rs.getBytes(2)); GroupId g = new GroupId(rs.getBytes(2));
Collection<MessageId> messageIds = ids.get(g); Collection<MessageId> messageIds = ids.get(g);
//noinspection Java8MapApi
if (messageIds == null) { if (messageIds == null) {
messageIds = new ArrayList<>(); messageIds = new ArrayList<>();
ids.put(g, messageIds); ids.put(g, messageIds);
@@ -2586,8 +2547,9 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public Collection<MessageId> getRequestedMessagesToSend(Connection txn, public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
ContactId c, long capacity, long maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2596,21 +2558,21 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE" + " AND deleted = FALSE"
+ " AND seen = FALSE AND requested = TRUE" + " AND seen = FALSE AND requested = TRUE"
+ " AND (expiry <= ? OR maxLatency IS NULL" + " AND (expiry <= ? OR eta > ?)"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp"; + " ORDER BY timestamp";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
ps.setLong(4, maxLatency); ps.setLong(4, eta);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) { while (rs.next()) {
int length = rs.getInt(1); int length = rs.getInt(1);
if (capacity < RECORD_HEADER_BYTES + length) break; if (total + length > maxLength) break;
ids.add(new MessageId(rs.getBytes(2))); ids.add(new MessageId(rs.getBytes(2)));
capacity -= RECORD_HEADER_BYTES + length; total += length;
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2764,7 +2726,6 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1)); ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2)); TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c); Collection<TransportId> transportIds = ids.get(c);
//noinspection Java8MapApi
if (transportIds == null) { if (transportIds == null) {
transportIds = new ArrayList<>(); transportIds = new ArrayList<>();
ids.put(c, transportIds); ids.put(c, transportIds);
@@ -3329,30 +3290,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void resetUnackedMessagesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE statuses SET expiry = 0, txCount = 0,"
+ " maxLatency = NULL"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
int affected = ps.executeUpdate();
if (affected < 0) {
throw new DbStateException();
}
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void setCleanupTimerDuration(Connection txn, MessageId m, public void setCleanupTimerDuration(Connection txn, MessageId m,
long duration) throws DbException { long duration) throws DbException {
@@ -3680,8 +3617,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void updateRetransmissionData(Connection txn, ContactId c, public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
MessageId m, long maxLatency) throws DbException { long maxLatency) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -3697,12 +3634,13 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close(); rs.close();
ps.close(); ps.close();
sql = "UPDATE statuses" sql = "UPDATE statuses"
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?" + " SET expiry = ?, txCount = txCount + 1, eta = ?"
+ " WHERE messageId = ? AND contactId = ?"; + " WHERE messageId = ? AND contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency;
ps.setLong(1, calculateExpiry(now, maxLatency, txCount)); ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
ps.setLong(2, maxLatency); ps.setLong(2, eta);
ps.setBytes(3, m.getBytes()); ps.setBytes(3, m.getBytes());
ps.setInt(4, c.getInt()); ps.setInt(4, c.getInt());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();

View File

@@ -1,45 +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 static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration49_50 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration49_50.class.getName());
@Override
public int getStartVersion() {
return 49;
}
@Override
public int getEndVersion() {
return 50;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE statuses"
+ " ALTER COLUMN eta"
+ " RENAME TO maxLatency");
s.execute("ALTER TABLE statuses"
+ " ALTER COLUMN maxLatency"
+ " SET NULL");
s.execute("UPDATE statuses SET maxLatency = NULL");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -1,49 +1,18 @@
package org.briarproject.bramble.io; package org.briarproject.bramble.io;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import javax.annotation.Nonnull;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.net.SocketFactory;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import okhttp3.Dns;
import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@Module @Module
public class IoModule { public class IoModule {
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
@Provides @Provides
@Singleton @Singleton
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) { TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
return timeoutMonitor; return timeoutMonitor;
} }
// Share an HTTP client instance between requests where possible, while
// allowing the client to be garbage-collected between requests. The
// provider keeps a weak reference to the last client instance and reuses
// the instance until it gets garbage-collected. See
// https://medium.com/@leandromazzuquini/if-you-are-using-okhttp-you-should-know-this-61d68e065a2b
@Provides
@Singleton
WeakSingletonProvider<OkHttpClient> provideOkHttpClientProvider(
SocketFactory torSocketFactory, Dns noDnsLookups) {
return new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return new OkHttpClient.Builder()
.socketFactory(torSocketFactory)
.dns(noDnsLookups) // Don't make local DNS lookups
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
.build();
}
};
}
} }

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.io; package org.briarproject.bramble.io;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.bramble.api.system.Wakeful;
import java.io.IOException; import java.io.IOException;

View File

@@ -1,21 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
/**
* An interface for calling an API endpoint with the option to retry the call.
*/
interface ApiCall {
/**
* This method makes a synchronous call to an API endpoint and returns
* true if the call should be retried, in which case the method may be
* called again on the same {@link ApiCall} instance after a delay.
*
* @return True if the API call needs to be retried, or false if the API
* call succeeded or {@link TolerableFailureException failed tolerably}.
*/
@IoExecutor
boolean callApi();
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
/**
* An interface for checking whether a mailbox is reachable.
*/
@ThreadSafe
@NotNullByDefault
interface ConnectivityChecker {
/**
* Destroys the checker. Any current connectivity check is cancelled.
*/
void destroy();
/**
* Starts a connectivity check if needed and calls the given observer when
* the check succeeds. If a check is already running then the observer is
* called when the check succeeds. If a connectivity check has recently
* succeeded then the observer is called immediately.
*/
void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o);
interface ConnectivityObserver {
void onConnectivityCheckSucceeded();
}
}

View File

@@ -1,111 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
/**
* If no more than this much time has elapsed since the last connectivity
* check succeeded, consider the result to be fresh and don't check again.
* <p>
* Package access for testing.
*/
static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000;
private final Object lock = new Object();
protected final Clock clock;
private final MailboxApiCaller mailboxApiCaller;
@GuardedBy("lock")
private boolean destroyed = false;
@GuardedBy("lock")
@Nullable
private Cancellable connectivityCheck = null;
@GuardedBy("lock")
private long lastConnectivityCheckSucceeded = 0;
@GuardedBy("lock")
private final List<ConnectivityObserver> connectivityObservers =
new ArrayList<>();
/**
* Creates an {@link ApiCall} for checking whether the mailbox is
* reachable. The {@link ApiCall} should call
* {@link #onConnectivityCheckSucceeded(long)} if the check succeeds.
*/
abstract ApiCall createConnectivityCheckTask(MailboxProperties properties);
ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) {
this.clock = clock;
this.mailboxApiCaller = mailboxApiCaller;
}
@Override
public void destroy() {
synchronized (lock) {
destroyed = true;
connectivityObservers.clear();
if (connectivityCheck != null) {
connectivityCheck.cancel();
connectivityCheck = null;
}
}
}
@Override
public void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o) {
boolean callNow = false;
synchronized (lock) {
if (destroyed) return;
if (connectivityCheck == null) {
// No connectivity check is running
long now = clock.currentTimeMillis();
if (now - lastConnectivityCheckSucceeded
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
// The last connectivity check is stale, start a new one
connectivityObservers.add(o);
ApiCall task =
createConnectivityCheckTask(properties);
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
} else {
// The last connectivity check is fresh
callNow = true;
}
} else {
// A connectivity check is running, wait for it to succeed
connectivityObservers.add(o);
}
}
if (callNow) o.onConnectivityCheckSucceeded();
}
protected void onConnectivityCheckSucceeded(long now) {
List<ConnectivityObserver> observers;
synchronized (lock) {
if (destroyed) return;
connectivityCheck = null;
lastConnectivityCheckSucceeded = now;
observers = new ArrayList<>(connectivityObservers);
connectivityObservers.clear();
}
for (ConnectivityObserver o : observers) {
o.onConnectivityCheckSucceeded();
}
}
}

View File

@@ -1,39 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private final MailboxApi mailboxApi;
ContactMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall() {
@Override
void tryToCallApi() throws IOException, ApiException {
if (!mailboxApi.checkStatus(properties)) {
throw new ApiException();
}
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
}
};
}
}

View File

@@ -1,193 +0,0 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonList;
@NotNullByDefault
interface MailboxApi {
/**
* Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}.
*/
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
new MailboxVersion(1, 0));
List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException;
/**
* Sets up the mailbox with the setup token.
*
* @param properties MailboxProperties with the setup token
* @return the owner token
* @throws ApiException for 401 response.
*/
MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException;
/**
* Checks the status of the mailbox.
*
* @return true if the status is OK, false otherwise.
* @throws ApiException for 401 response.
*/
boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException;
/**
* Unpairs Briar and the mailbox (owner only).
* Resets mailbox state to that after first install
* (e.g. removes all stored files as well).
*/
void wipeMailbox(MailboxProperties properties)
throws IOException, ApiException;
/**
* Adds a new contact to the mailbox.
*
* @throws TolerableFailureException if response code is 409
* (contact was already added).
*/
void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException, TolerableFailureException;
/**
* Deletes a contact from the mailbox.
* This should get called after a contact was removed from Briar.
*
* @throws TolerableFailureException if response code is 404
* (contact probably was already deleted).
*/
void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException;
/**
* Gets a list of {@link ContactId}s from the mailbox.
* These are the contacts that the mailbox already knows about.
*/
Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException;
/**
* Used by contacts to send files to the owner
* and by the owner to send files to contacts.
* <p>
* The owner can add files to the contacts' inboxes
* and the contacts can add files to their own outbox.
*/
void addFile(MailboxProperties properties, MailboxFolderId folderId,
File file) throws IOException, ApiException;
/**
* Used by owner and contacts to list their files to retrieve.
* <p>
* Returns 200 OK with the list of files in JSON.
*/
List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) throws IOException, ApiException;
/**
* Used by owner and contacts to retrieve a file.
* <p>
* Returns 200 OK if successful with the files' raw bytes
* in the response body.
*
* @param file the empty file the response bytes will be written into.
*/
void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) throws IOException, ApiException;
/**
* Used by owner and contacts to delete files.
* <p>
* Returns 200 OK (no exception) if deletion was successful.
*
* @throws TolerableFailureException on 404 response,
* because file was most likely deleted already.
*/
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId)
throws IOException, ApiException, TolerableFailureException;
/**
* Lists all contact outboxes that have files available
* for the owner to download.
*
* @return a list of folder names
* to be used with {@link #getFiles(MailboxProperties, MailboxFolderId)}.
* @throws IllegalArgumentException if used by non-owner.
*/
List<MailboxFolderId> getFolders(MailboxProperties properties)
throws IOException, ApiException;
@Immutable
@JsonSerialize
class MailboxContact {
public final int contactId;
public final MailboxAuthToken token;
public final MailboxFolderId inboxId, outboxId;
MailboxContact(ContactId contactId,
MailboxAuthToken token,
MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.contactId = contactId.getInt();
this.token = token;
this.inboxId = inboxId;
this.outboxId = outboxId;
}
}
@JsonSerialize
class MailboxFile implements Comparable<MailboxFile> {
public final MailboxFileId name;
public final long time;
public MailboxFile(MailboxFileId name, long time) {
this.name = name;
this.time = time;
}
@Override
public int compareTo(@Nonnull MailboxApi.MailboxFile mailboxFile) {
//noinspection UseCompareMethod
return time < mailboxFile.time ? -1 :
(time == mailboxFile.time ? 0 : 1);
}
}
@Immutable
class ApiException extends Exception {
}
@Immutable
class MailboxAlreadyPairedException extends ApiException {
}
/**
* A failure that does not need to be retried,
* e.g. when adding a contact that already exists.
*/
@Immutable
class TolerableFailureException extends Exception {
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
@NotNullByDefault
interface MailboxApiCaller {
/**
* The minimum interval between retries in milliseconds.
*/
long MIN_RETRY_INTERVAL_MS = MINUTES.toMillis(1);
/**
* The maximum interval between retries in milliseconds.
*/
long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1);
/**
* Asynchronously calls the given API call on the {@link IoExecutor},
* automatically retrying at increasing intervals until the API call
* returns false or retries are cancelled.
*
* @return A {@link Cancellable} that can be used to cancel any future
* retries.
*/
Cancellable retryWithBackoff(ApiCall apiCall);
}

View File

@@ -1,98 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@Immutable
@NotNullByDefault
class MailboxApiCallerImpl implements MailboxApiCaller {
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
@Inject
MailboxApiCallerImpl(TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor) {
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
}
@Override
public Cancellable retryWithBackoff(ApiCall apiCall) {
Task task = new Task(apiCall);
task.start();
return task;
}
private class Task implements Cancellable {
private final ApiCall apiCall;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private Cancellable scheduledTask = null;
@GuardedBy("lock")
private boolean cancelled = false;
@GuardedBy("lock")
private long retryIntervalMs = MIN_RETRY_INTERVAL_MS;
private Task(ApiCall apiCall) {
this.apiCall = apiCall;
}
private void start() {
synchronized (lock) {
if (cancelled) throw new AssertionError();
ioExecutor.execute(this::callApi);
}
}
@IoExecutor
private void callApi() {
synchronized (lock) {
if (cancelled) return;
}
// The call returns true if we should retry
if (apiCall.callApi()) {
synchronized (lock) {
if (cancelled) return;
scheduledTask = taskScheduler.schedule(this::callApi,
ioExecutor, retryIntervalMs, MILLISECONDS);
// Increase the retry interval each time we retry
retryIntervalMs =
min(MAX_RETRY_INTERVAL_MS, retryIntervalMs * 2);
}
} else {
synchronized (lock) {
scheduledTask = null;
}
}
}
@Override
public void cancel() {
Cancellable scheduledTask;
synchronized (lock) {
cancelled = true;
scheduledTask = this.scheduledTask;
this.scheduledTask = null;
}
if (scheduledTask != null) scheduledTask.cancel();
}
}
}

View File

@@ -1,342 +0,0 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@NotNullByDefault
class MailboxApiImpl implements MailboxApi {
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private static final MediaType JSON =
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
private static final MediaType FILE =
requireNonNull(MediaType.parse("application/octet-stream"));
@Inject
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
this.httpClientProvider = httpClientProvider;
}
@Override
public List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/versions");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
return parseServerSupports(node);
} catch (JacksonException e) {
throw new ApiException();
}
}
@Override
public MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/setup")
.put(EMPTY_REQUEST)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 401) throw new MailboxAlreadyPairedException();
if (!response.isSuccessful()) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
JsonNode tokenNode = node.get("token");
if (tokenNode == null) {
throw new ApiException();
}
return new MailboxProperties(properties.getBaseUrl(),
MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node));
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
private List<MailboxVersion> parseServerSupports(JsonNode node)
throws ApiException {
List<MailboxVersion> serverSupports = new ArrayList<>();
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
for (JsonNode versionNode : serverSupportsNode) {
if (!versionNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) versionNode;
JsonNode majorNode = objectNode.get("major");
JsonNode minorNode = objectNode.get("minor");
if (majorNode == null || !majorNode.isNumber()) {
throw new ApiException();
}
if (minorNode == null || !minorNode.isNumber()) {
throw new ApiException();
}
int major = majorNode.asInt();
int minor = minorNode.asInt();
if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor));
}
return serverSupports;
}
@Override
public boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException {
Response response = sendGetRequest(properties, "/status");
if (response.code() == 401) throw new ApiException();
return response.isSuccessful();
}
@Override
public void wipeMailbox(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/")
.delete()
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() != 204) throw new ApiException();
}
/* Contact Management API (owner only) */
@Override
public void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
byte[] bodyBytes = mapper.writeValueAsBytes(contact);
RequestBody body = RequestBody.create(JSON, bodyBytes);
Response response = sendPostRequest(properties, "/contacts", body);
if (response.code() == 409) throw new TolerableFailureException();
if (!response.isSuccessful()) throw new ApiException();
}
@Override
public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
String url = properties.getBaseUrl() + "/contacts/" +
contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(url)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException();
}
@Override
public Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/contacts");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode contactsNode = getArray(node, "contacts");
List<ContactId> list = new ArrayList<>();
for (JsonNode contactNode : contactsNode) {
if (!contactNode.isNumber()) throw new ApiException();
int id = contactNode.intValue();
if (id < 1) throw new ApiException();
list.add(new ContactId(id));
}
return list;
} catch (JacksonException e) {
throw new ApiException();
}
}
/* File Management (owner and contacts) */
@Override
public void addFile(MailboxProperties properties, MailboxFolderId folderId,
File file) throws IOException, ApiException {
String path = "/files/" + folderId;
RequestBody body = RequestBody.create(FILE, file);
Response response = sendPostRequest(properties, path, body);
if (response.code() != 200) throw new ApiException();
}
@Override
public List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) throws IOException, ApiException {
String path = "/files/" + folderId;
Response response = sendGetRequest(properties, path);
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode filesNode = getArray(node, "files");
List<MailboxFile> list = new ArrayList<>();
for (JsonNode fileNode : filesNode) {
if (!fileNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) fileNode;
JsonNode nameNode = objectNode.get("name");
JsonNode timeNode = objectNode.get("time");
if (nameNode == null || !nameNode.isTextual()) {
throw new ApiException();
}
if (timeNode == null || !timeNode.isNumber()) {
throw new ApiException();
}
String name = nameNode.asText();
long time = timeNode.asLong();
if (time < 1) throw new ApiException();
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
}
Collections.sort(list);
return list;
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
@Override
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) throws IOException, ApiException {
String path = "/files/" + folderId + "/" + fileId;
Response response = sendGetRequest(properties, path);
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
FileOutputStream outputStream = new FileOutputStream(file);
copyAndClose(body.byteStream(), outputStream);
}
@Override
public void deleteFile(MailboxProperties properties,
MailboxFolderId folderId, MailboxFileId fileId)
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId + "/" + fileId;
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(properties.getBaseUrl() + path)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException();
}
@Override
public List<MailboxFolderId> getFolders(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/folders");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode filesNode = getArray(node, "folders");
List<MailboxFolderId> list = new ArrayList<>();
for (JsonNode fileNode : filesNode) {
if (!fileNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) fileNode;
JsonNode idNode = objectNode.get("id");
if (idNode == null || !idNode.isTextual()) {
throw new ApiException();
}
String id = idNode.asText();
list.add(MailboxFolderId.fromString(id));
}
return list;
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
/* Helper Functions */
private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
}
private Response sendPostRequest(MailboxProperties properties, String path,
RequestBody body) throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.post(body)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
}
private Request.Builder getRequestBuilder(MailboxId token) {
return new Request.Builder()
.addHeader("Authorization", "Bearer " + token);
}
/* JSON helpers */
private ArrayNode getArray(JsonNode node, String name) throws ApiException {
JsonNode arrayNode = node.get(name);
if (arrayNode == null || !arrayNode.isArray()) {
throw new ApiException();
}
return (ArrayNode) arrayNode;
}
}

View File

@@ -1,156 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
class MailboxManagerImpl implements MailboxManager {
private static final String TAG = MailboxManagerImpl.class.getName();
private final static Logger LOG = getLogger(TAG);
private final Executor ioExecutor;
private final MailboxApi api;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxPairingTaskFactory pairingTaskFactory;
private final Clock clock;
private final Object lock = new Object();
@Nullable
@GuardedBy("lock")
private MailboxPairingTask pairingTask = null;
@Inject
MailboxManagerImpl(
@IoExecutor Executor ioExecutor,
MailboxApi api,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager,
MailboxPairingTaskFactory pairingTaskFactory,
Clock clock) {
this.ioExecutor = ioExecutor;
this.api = api;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
this.pairingTaskFactory = pairingTaskFactory;
this.clock = clock;
}
@Override
public boolean isPaired(Transaction txn) throws DbException {
return mailboxSettingsManager.getOwnMailboxProperties(txn) != null;
}
@Override
public MailboxStatus getMailboxStatus(Transaction txn) throws DbException {
return mailboxSettingsManager.getOwnMailboxStatus(txn);
}
@Nullable
@Override
public MailboxPairingTask getCurrentPairingTask() {
synchronized (lock) {
return pairingTask;
}
}
@Override
public MailboxPairingTask startPairingTask(String payload) {
MailboxPairingTask created;
synchronized (lock) {
if (pairingTask != null) return pairingTask;
created = pairingTaskFactory.createPairingTask(payload);
pairingTask = created;
}
ioExecutor.execute(() -> {
created.run();
synchronized (lock) {
// remove task after it finished
pairingTask = null;
}
});
return created;
}
@Override
public boolean checkConnection() {
boolean success;
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException();
success = api.checkStatus(props);
} catch (DbException e) {
logException(LOG, WARNING, e);
// we don't treat this is a failure to record
return false;
} catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure
success = false;
logException(LOG, WARNING, e);
}
try {
recordCheckResult(success);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
return success;
}
private void recordCheckResult(boolean success) throws DbException {
long now = clock.currentTimeMillis();
db.transaction(false, txn -> {
if (success) {
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
} else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
}
});
}
@Override
public boolean unPair() throws DbException {
MailboxProperties properties = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (properties == null) {
// no more mailbox, that's strange but possible if called in quick
// succession, so let's return true this time
return true;
}
boolean wasWiped;
try {
api.wipeMailbox(properties);
wasWiped = true;
} catch (IOException | MailboxApi.ApiException e) {
logException(LOG, WARNING, e);
wasWiped = false;
}
db.transaction(false,
mailboxSettingsManager::removeOwnMailboxProperties);
return wasWiped;
}
}

View File

@@ -1,104 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
@Module
public class MailboxModule {
public static class EagerSingletons {
@Inject
MailboxUpdateValidator mailboxUpdateValidator;
@Inject
MailboxUpdateManager mailboxUpdateManager;
}
@Provides
@Singleton
MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) {
return mailboxManager;
}
@Provides
MailboxPairingTaskFactory provideMailboxPairingTaskFactory(
MailboxPairingTaskFactoryImpl mailboxPairingTaskFactory) {
return mailboxPairingTaskFactory;
}
@Provides
MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager;
}
@Provides
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi;
}
@Provides
@Singleton
MailboxUpdateValidator provideMailboxUpdateValidator(
ValidationManager validationManager,
ClientHelper clientHelper,
MetadataEncoder metadataEncoder,
Clock clock,
FeatureFlags featureFlags) {
MailboxUpdateValidator validator = new MailboxUpdateValidator(
clientHelper, metadataEncoder, clock);
if (featureFlags.shouldEnableMailbox()) {
validationManager.registerMessageValidator(CLIENT_ID,
MAJOR_VERSION, validator);
}
return validator;
}
@Provides
List<MailboxVersion> provideClientSupports() {
return CLIENT_SUPPORTS;
}
@Provides
@Singleton
MailboxUpdateManager provideMailboxUpdateManager(
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManagerImpl mailboxUpdateManager) {
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
}
return mailboxUpdateManager;
}
}

View File

@@ -1,12 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface MailboxPairingTaskFactory {
MailboxPairingTask createPairingTask(String qrCodePayload);
}

View File

@@ -1,53 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final Executor eventExecutor;
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxPairingTaskFactoryImpl(
@EventExecutor Executor eventExecutor,
DatabaseComponent db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager);
}
}

View File

@@ -1,186 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload;
private final Executor eventExecutor;
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<MailboxPairingState>> observers =
new ArrayList<>();
@GuardedBy("lock")
private MailboxPairingState state;
MailboxPairingTaskImpl(
String payload,
@EventExecutor Executor eventExecutor,
DatabaseComponent db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
state = new MailboxPairingState.QrCodeReceived();
}
@Override
public void addObserver(Consumer<MailboxPairingState> o) {
MailboxPairingState state;
synchronized (lock) {
observers.add(o);
state = this.state;
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<MailboxPairingState> o) {
synchronized (lock) {
observers.remove(o);
}
}
@Override
public void run() {
try {
pairMailbox();
} catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode());
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError());
} catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError());
}
}
private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing());
MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis();
db.transaction(false, txn -> {
mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
// A (possibly new) mailbox is paired. Reset message retransmission
// timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate update = mailboxUpdateManager.getRemoteUpdate(
txn, c.getId());
if (update == null || !update.hasMailbox()) {
db.resetUnackedMessagesToSend(txn, c.getId());
}
}
});
setState(new MailboxPairingState.Paired());
}
private void onMailboxError(Exception e, MailboxPairingState state) {
logException(LOG, WARNING, e);
setState(state);
}
private void setState(MailboxPairingState state) {
synchronized (lock) {
this.state = state;
notifyObservers();
}
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<MailboxPairingState>> observers =
new ArrayList<>(this.observers);
MailboxPairingState state = this.state;
eventExecutor.execute(() -> {
for (Consumer<MailboxPairingState> o : observers) o.accept(state);
});
}
private MailboxProperties decodeQrCodePayload(String payload)
throws FormatException {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length != 65) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code length is not 65: " + bytes.length);
}
throw new FormatException();
}
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
}
}

View File

@@ -1,168 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxProblemEvent;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
// Package access for testing
static final String SETTINGS_NAMESPACE = "mailbox";
// TODO: This currently stores the base URL, not the 56-char onion address
static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token";
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads";
private final SettingsManager settingsManager;
private final List<MailboxHook> hooks = new CopyOnWriteArrayList<>();
@Inject
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
this.settingsManager = settingsManager;
}
@Override
public void registerMailboxHook(MailboxHook hook) {
hooks.add(hook);
}
@Override
public MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
// We know we were paired, so we must have proper serverSupports
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
throw new DbException();
}
List<MailboxVersion> serverSupports = new ArrayList<>();
for (int i = 0; i < ints.length - 1; i += 2) {
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
}
try {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, serverSupports);
} catch (InvalidMailboxIdException e) {
throw new DbException(e);
}
}
@Override
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports();
int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
}
}
@Override
public void removeOwnMailboxProperties(Transaction txn) throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn);
}
}
@Override
public MailboxStatus getOwnMailboxStatus(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings s = new Settings();
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
MailboxStatus status = new MailboxStatus(now, now, 0);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@Override
public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
Settings newSettings = new Settings();
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
}
@Override
public void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException {
Settings s = new Settings();
String value = filename == null ? "" : filename;
s.put(String.valueOf(id.getInt()), value);
settingsManager.mergeSettings(txn, s, SETTINGS_UPLOADS_NAMESPACE);
}
@Nullable
@Override
public String getPendingUpload(Transaction txn, ContactId id)
throws DbException {
Settings s =
settingsManager.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
String filename = s.get(String.valueOf(id.getInt()));
if (isNullOrEmpty(filename)) return null;
return filename;
}
}

View File

@@ -1,373 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
@NotNullByDefault
class MailboxUpdateManagerImpl implements MailboxUpdateManager,
OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook, MailboxHook {
private final List<MailboxVersion> clientSupports;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final ClientVersioningManager clientVersioningManager;
private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory;
private final Clock clock;
private final MailboxSettingsManager mailboxSettingsManager;
private final CryptoComponent crypto;
private final Group localGroup;
@Inject
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports,
DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock,
MailboxSettingsManager mailboxSettingsManager,
CryptoComponent crypto) {
this.clientSupports = clientSupports;
this.db = db;
this.clientHelper = clientHelper;
this.clientVersioningManager = clientVersioningManager;
this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory;
this.clock = clock;
this.mailboxSettingsManager = mailboxSettingsManager;
this.crypto = crypto;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) {
try {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
txn, localGroup.getId());
BdfList sent = meta.getList(GROUP_KEY_SENT_CLIENT_SUPPORTS);
if (clientHelper.parseMailboxVersionList(sent)
.equals(clientSupports)) {
return;
}
} catch (FormatException e) {
throw new DbException();
}
// Our current clientSupports list has changed compared to what we
// last sent out.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate latest = getLocalUpdate(txn, c.getId());
MailboxUpdate updated;
if (latest.hasMailbox()) {
updated = new MailboxUpdateWithMailbox(
(MailboxUpdateWithMailbox) latest, clientSupports);
} else {
updated = new MailboxUpdate(clientSupports);
}
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), updated);
}
} else {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) {
addingContact(txn, c);
}
}
try {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS,
encodeSupportsList(clientSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
}
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
db.addGroup(txn, g);
// Apply the client's visibility to the contact group
Visibility client = clientVersioningManager
.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn);
if (ownProps != null) {
// We are paired, create and send props to the newly added contact
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getOnion());
} else {
// Not paired, but we still want to get our clientSupports sent
sendUpdateNoMailbox(txn, c);
}
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c));
}
@Override
public void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports) throws DbException {
for (Contact c : db.getContacts(txn)) {
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
}
}
@Override
public void mailboxUnpaired(Transaction txn) throws DbException {
for (Contact c : db.getContacts(txn)) {
sendUpdateNoMailbox(txn, c);
}
}
@Override
public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException {
// Apply the client's visibility to the contact group
Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
}
@Override
public DeliveryAction incomingMessage(Transaction txn, Message m,
Metadata meta) throws DbException, InvalidMessageException {
try {
BdfDictionary d = metadataParser.parse(meta);
// Get latest non-local update in the same group (from same contact)
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
if (latest != null) {
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
db.deleteMessage(txn, latest.messageId);
db.deleteMessageMetadata(txn, latest.messageId);
} else {
// Delete this update, we already have a newer one
db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId());
return ACCEPT_DO_NOT_SHARE;
}
}
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
MailboxUpdate u = parseUpdate(body);
txn.attach(new RemoteMailboxUpdateEvent(c, u));
// Reset message retransmission timers for the contact. Avoiding
// messages getting stranded:
// - on our mailbox, if they now have a mailbox but didn't before
// - on the contact's old mailbox, if they removed their mailbox
// - on the contact's old mailbox, if they replaced their mailbox
db.resetUnackedMessagesToSend(txn, c);
} catch (FormatException e) {
throw new InvalidMessageException(e);
}
return ACCEPT_DO_NOT_SHARE;
}
@Override
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException {
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true);
// An update (with or without mailbox) is created when contact is added
if (local == null) {
throw new DbException();
}
return local;
}
@Override
@Nullable
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException {
return getUpdate(txn, db.getContact(txn, c), false);
}
/**
* Creates and sends an update message to the given contact. The message
* holds our own mailbox's onion, generated unique properties, and lists of
* supported Mailbox API version(s). All of which the contact needs to
* communicate with our Mailbox.
*/
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
List<MailboxVersion> serverSupports, String ownOnion)
throws DbException {
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u);
}
/**
* Sends an update message with empty properties to the given contact. The
* empty update indicates for the receiving contact that we don't have any
* Mailbox that they can use. It still includes the list of Mailbox API
* version(s) that we support as a client.
*/
private void sendUpdateNoMailbox(Transaction txn, Contact c)
throws DbException {
Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports);
storeMessageReplaceLatest(txn, g.getId(), u);
}
@Nullable
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local)
throws DbException {
MailboxUpdate u = null;
Group g = getContactGroup(c);
try {
LatestUpdate latest = findLatest(txn, g.getId(), local);
if (latest != null) {
BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId);
u = parseUpdate(body);
}
} catch (FormatException e) {
throw new DbException(e);
}
return u;
}
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u) throws DbException {
try {
LatestUpdate latest = findLatest(txn, g, true);
long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
} catch (FormatException e) {
throw new DbException(e);
}
}
@Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
// We should have at most 1 local and 1 remote
if (metadata.size() > 2) {
throw new IllegalStateException();
}
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
return new LatestUpdate(e.getKey(),
meta.getLong(MSG_KEY_VERSION));
}
}
return null;
}
private BdfList encodeProperties(long version, MailboxUpdate u) {
BdfDictionary dict = new BdfDictionary();
BdfList serverSupports = new BdfList();
if (u.hasMailbox()) {
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u;
MailboxProperties properties = um.getMailboxProperties();
serverSupports = encodeSupportsList(properties.getServerSupports());
dict.put(PROP_KEY_ONION, properties.getOnion());
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
}
return BdfList.of(version, encodeSupportsList(u.getClientSupports()),
serverSupports, dict);
}
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) {
BdfList supports = new BdfList();
for (MailboxVersion version : supportsList) {
supports.add(BdfList.of(version.getMajor(), version.getMinor()));
}
return supports;
}
private MailboxUpdate parseUpdate(BdfList body) throws FormatException {
BdfList clientSupports = body.getList(1);
BdfList serverSupports = body.getList(2);
BdfDictionary dict = body.getDictionary(3);
return clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dict);
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
c);
}
private static class LatestUpdate {
private final MessageId messageId;
private final long version;
private LatestUpdate(MessageId messageId, long version) {
this.messageId = messageId;
this.version = version;
}
}
}

View File

@@ -1,54 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
class MailboxUpdateValidator extends BdfMessageValidator {
MailboxUpdateValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// Version, Properties, clientSupports, serverSupports
checkSize(body, 4);
// Version
long version = body.getLong(0);
if (version < 0) throw new FormatException();
// clientSupports
BdfList clientSupports = body.getList(1);
// serverSupports
BdfList serverSupports = body.getList(2);
// Properties
BdfDictionary dictionary = body.getDictionary(3);
clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta);
}
}

View File

@@ -1,75 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(OwnMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
return () -> {
try {
return checkConnectivityAndStoreResult(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
return true; // Retry
}
};
}
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now));
// Call the observers and cache the result
onConnectivityCheckSucceeded(now);
return false; // Don't retry
} catch (IOException | ApiException e) {
LOG.warning("Own mailbox is unreachable");
logException(LOG, WARNING, e);
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordFailedConnectionAttempt(txn, now));
}
return true; // Retry
}
}

View File

@@ -1,39 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
/**
* Convenience class for making simple API calls that don't return values.
*/
@NotNullByDefault
public abstract class SimpleApiCall implements ApiCall {
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
abstract void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
@Override
public boolean callApi() {
try {
tryToCallApi();
return false; // Succeeded, don't retry
} catch (IOException | ApiException e) {
logException(LOG, WARNING, e);
return true; // Failed, retry with backoff
} catch (TolerableFailureException e) {
logException(LOG, INFO, e);
return false; // Failed tolerably, don't retry
}
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
@@ -28,6 +27,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask; import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@Deprecated // We can simply remove tasks when they finish
@NotNullByDefault @NotNullByDefault
interface RemovableDriveTaskRegistry { interface RemovableDriveTaskRegistry {

View File

@@ -1,73 +1,40 @@
package org.briarproject.bramble.plugin.tor; package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List; import java.util.List;
@NotNullByDefault // TODO: Create a module for this so it doesn't have to be public
public interface CircumventionProvider {
enum BridgeType { public interface CircumventionProvider {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
VANILLA,
MEEK
}
/** /**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work. * Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
* <p> *
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 * See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki * and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/ */
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"}; String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
/** /**
* Countries where bridge connections are likely to work. * Countries where obfs4 or meek bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of * Should be a subset of {@link #BLOCKED}.
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #MEEK_BRIDGES}.
*/ */
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"}; String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
/** /**
* Countries where default obfs4 or vanilla bridges are likely to work. * Countries where obfs4 bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}. * Should be a subset of {@link #BRIDGES}.
*/ */
String[] DEFAULT_BRIDGES = {"EG", "VE"}; String[] NEEDS_MEEK = {"CN", "IR"};
/**
* Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
/**
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] MEEK_BRIDGES = {"CN", "IR"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode); boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode); boolean doBridgesWork(String countryCode);
/** boolean needsMeek(String countryCode);
* Returns the types of bridge connection that are suitable for the given
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
* to work.
*/
List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor @IoExecutor
List<String> getBridges(BridgeType type); List<String> getBridges(boolean meek);
} }

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