mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
2 Commits
checkstyle
...
1387-persi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b9955d71f | ||
|
|
719e3c6138 |
@@ -32,9 +32,8 @@ test:
|
||||
extends: .base-test
|
||||
stage: test
|
||||
script:
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom assembleOfficialDebug :briar-headless:linuxJars
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: always
|
||||
@@ -62,7 +61,7 @@ android test:
|
||||
when: on_failure
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: manual
|
||||
when: on_success
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
changes:
|
||||
- briar-android/**/*
|
||||
@@ -85,36 +84,35 @@ test_reproducible:
|
||||
|
||||
.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:
|
||||
extends: .optional_tests
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
allow_failure: false
|
||||
allow_failure: true
|
||||
- if: '$CI_COMMIT_TAG == null'
|
||||
when: manual
|
||||
allow_failure: true
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 3h
|
||||
|
||||
mailbox integration test:
|
||||
pre_release_tests:
|
||||
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
|
||||
only:
|
||||
- tags
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Folder-Description:
|
||||
===================
|
||||
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
|
||||
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
|
||||
|
||||
---
|
||||
|
||||
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
|
||||
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
|
||||
* `*-java`: implementations of api that are specific to Desktop & Headless
|
||||
@@ -22,15 +22,6 @@ our site.
|
||||
|
||||
[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
|
||||
|
||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10411
|
||||
versionName "1.4.11"
|
||||
versionCode 10401
|
||||
versionName "1.4.1"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -43,7 +43,7 @@ configurations {
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
tor "org.briarproject:tor-android:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -53,7 +53,7 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$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'
|
||||
@@ -70,6 +70,11 @@ clean.dependsOn cleanTorBinaries
|
||||
|
||||
task unpackTorBinaries {
|
||||
doLast {
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into torBinariesDir
|
||||
include 'geoip.zip'
|
||||
}
|
||||
configurations.tor.each { outer ->
|
||||
zipTree(outer).each { inner ->
|
||||
if (inner.name.endsWith('_arm_pie.zip')) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Keep the H2 classes that are loaded via reflection
|
||||
-keep class org.h2.Driver { *; }
|
||||
-keep class org.h2.engine.Engine { *; }
|
||||
-keep class org.h2.store.fs.** { *; }
|
||||
# Don't warn about unused dependencies of H2 classes
|
||||
-keep,includedescriptorclasses class org.briarproject.** { *; }
|
||||
|
||||
-keep class org.h2.** { *; }
|
||||
-dontwarn org.h2.**
|
||||
-dontnote org.h2.**
|
||||
|
||||
@@ -17,8 +15,5 @@
|
||||
-dontwarn sun.misc.Unsafe
|
||||
-dontnote com.google.common.**
|
||||
|
||||
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
||||
|
||||
# Keep all Jackson-serialisable classes and their members
|
||||
-keep interface com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class * { *; }
|
||||
# UPnP library isn't used
|
||||
-dontwarn org.bitlet.weupnp.**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.battery.AndroidBatteryModule;
|
||||
import org.briarproject.bramble.io.DnsModule;
|
||||
import org.briarproject.bramble.network.AndroidNetworkModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
||||
import org.briarproject.bramble.reporting.ReportingModule;
|
||||
@@ -19,7 +18,6 @@ import dagger.Module;
|
||||
AndroidTaskSchedulerModule.class,
|
||||
AndroidWakefulIoExecutorModule.class,
|
||||
CircumventionModule.class,
|
||||
DnsModule.class,
|
||||
ReportingModule.class,
|
||||
SocksModule.class
|
||||
})
|
||||
|
||||
@@ -5,12 +5,15 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.account.AccountManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -23,14 +26,18 @@ import javax.inject.Inject;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.Arrays.asList;
|
||||
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.AndroidUtils.getPersistentLogDir;
|
||||
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
||||
|
||||
class AndroidAccountManager extends AccountManagerImpl
|
||||
implements AccountManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidAccountManager.class.getName());
|
||||
getLogger(AndroidAccountManager.class.getName());
|
||||
|
||||
/**
|
||||
* Directories that shouldn't be deleted when deleting the user's account.
|
||||
@@ -40,13 +47,22 @@ class AndroidAccountManager extends AccountManagerImpl
|
||||
|
||||
protected final Context appContext;
|
||||
private final SharedPreferences prefs;
|
||||
private final PersistentLogManager logManager;
|
||||
private final FeatureFlags featureFlags;
|
||||
|
||||
@Inject
|
||||
AndroidAccountManager(DatabaseConfig databaseConfig,
|
||||
CryptoComponent crypto, IdentityManager identityManager,
|
||||
SharedPreferences prefs, Application app) {
|
||||
AndroidAccountManager(
|
||||
DatabaseConfig databaseConfig,
|
||||
CryptoComponent crypto,
|
||||
IdentityManager identityManager,
|
||||
SharedPreferences prefs,
|
||||
PersistentLogManager logManager,
|
||||
FeatureFlags featureFlags,
|
||||
Application app) {
|
||||
super(databaseConfig, crypto, identityManager);
|
||||
this.prefs = prefs;
|
||||
this.logManager = logManager;
|
||||
this.featureFlags = featureFlags;
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@@ -74,6 +90,9 @@ class AndroidAccountManager extends AccountManagerImpl
|
||||
LOG.info("Contents of account directory after deleting:");
|
||||
logFileOrDir(LOG, INFO, getDataDir());
|
||||
}
|
||||
if (featureFlags.shouldEnablePersistentLogs()) {
|
||||
replacePersistentLogger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,4 +153,13 @@ class AndroidAccountManager extends AccountManagerImpl
|
||||
private void addIfNotNull(Set<File> files, @Nullable File file) {
|
||||
if (file != null) files.add(file);
|
||||
}
|
||||
|
||||
private void replacePersistentLogger() {
|
||||
File logDir = getPersistentLogDir(appContext);
|
||||
try {
|
||||
logManager.addLogHandler(logDir, getLogger(""));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
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.EventExecutor;
|
||||
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.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
@@ -40,7 +38,6 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
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_ON;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
@@ -114,37 +111,15 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
||||
|
||||
@Override
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
// https://issuetracker.google.com/issues/175055271
|
||||
try {
|
||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||
boolean connected = net != null && net.isConnected();
|
||||
boolean wifi = false, ipv6Only = false;
|
||||
if (connected) {
|
||||
wifi = net.getType() == TYPE_WIFI;
|
||||
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);
|
||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||
boolean connected = net != null && net.isConnected();
|
||||
boolean wifi = false, ipv6Only = false;
|
||||
if (connected) {
|
||||
wifi = net.getType() == TYPE_WIFI;
|
||||
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
||||
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
||||
}
|
||||
return new NetworkStatus(connected, wifi, ipv6Only);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,29 +130,23 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
||||
*/
|
||||
@TargetApi(23)
|
||||
private boolean isActiveNetworkIpv6Only() {
|
||||
// https://issuetracker.google.com/issues/175055271
|
||||
try {
|
||||
Network net = connectivityManager.getActiveNetwork();
|
||||
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);
|
||||
Network net = connectivityManager.getActiveNetwork();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,22 +32,13 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
try {
|
||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||
} catch (SecurityException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
try {
|
||||
return app.getContentResolver()
|
||||
.openOutputStream(Uri.parse(uri), "wt");
|
||||
} catch (SecurityException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,24 +175,16 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
@TargetApi(21)
|
||||
@Nullable
|
||||
private InetAddress getWifiClientIpv6Address() {
|
||||
// https://issuetracker.google.com/issues/175055271
|
||||
try {
|
||||
for (Network net : connectivityManager.getAllNetworks()) {
|
||||
NetworkCapabilities caps =
|
||||
connectivityManager.getNetworkCapabilities(net);
|
||||
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) {
|
||||
continue;
|
||||
}
|
||||
LinkProperties props =
|
||||
connectivityManager.getLinkProperties(net);
|
||||
if (props == null) continue;
|
||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
||||
InetAddress addr = linkAddress.getAddress();
|
||||
if (isIpv6LinkLocalAddress(addr)) return addr;
|
||||
}
|
||||
for (Network net : connectivityManager.getAllNetworks()) {
|
||||
NetworkCapabilities caps =
|
||||
connectivityManager.getNetworkCapabilities(net);
|
||||
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
|
||||
LinkProperties props = 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;
|
||||
}
|
||||
@@ -235,17 +227,12 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
// network's socket factory may try to connect via another network
|
||||
private SocketFactory getSocketFactory() {
|
||||
if (SDK_INT < 21) return SocketFactory.getDefault();
|
||||
// https://issuetracker.google.com/issues/175055271
|
||||
try {
|
||||
for (Network net : connectivityManager.getAllNetworks()) {
|
||||
NetworkCapabilities caps =
|
||||
connectivityManager.getNetworkCapabilities(net);
|
||||
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
||||
return net.getSocketFactory();
|
||||
}
|
||||
for (Network net : connectivityManager.getAllNetworks()) {
|
||||
NetworkCapabilities caps =
|
||||
connectivityManager.getNetworkCapabilities(net);
|
||||
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");
|
||||
return SocketFactory.getDefault();
|
||||
|
||||
@@ -11,35 +11,62 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
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.TorDirectory;
|
||||
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.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
||||
|
||||
@Immutable
|
||||
@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 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 Clock clock;
|
||||
private final File torDirectory;
|
||||
private int torSocksPort;
|
||||
private int torControlPort;
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
Application app,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
@@ -48,43 +75,80 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
AndroidWakeLockManager wakeLockManager,
|
||||
Clock clock,
|
||||
CryptoComponent crypto,
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort,
|
||||
Application app,
|
||||
AndroidWakeLockManager wakeLockManager) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
CryptoComponent crypto) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
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.clock = clock;
|
||||
this.torDirectory = torDirectory;
|
||||
this.torSocksPort = torSocksPort;
|
||||
this.torControlPort = torControlPort;
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getArchitectureForTorBinary() {
|
||||
for (String abi : getSupportedArchitectures()) {
|
||||
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";
|
||||
else if (abi.startsWith("armeabi")) return "arm_pie";
|
||||
public TransportId getId() {
|
||||
return TorConstants.ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new AndroidTorPlugin(ioExecutor,
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
TorRendezvousCrypto torRendezvousCrypto =
|
||||
new TorRendezvousCryptoImpl(crypto);
|
||||
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
|
||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, wakeLockManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.content.Intent;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AlarmListener;
|
||||
@@ -117,12 +116,10 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
||||
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
||||
Runnable wakeful = () ->
|
||||
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
||||
// Acquire the lock before scheduling the check to ensure the check
|
||||
// doesn't access the task queue before the task has been added
|
||||
ScheduledTask s;
|
||||
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
||||
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
|
||||
cancelled);
|
||||
synchronized (lock) {
|
||||
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
||||
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
|
||||
tasks.add(s);
|
||||
}
|
||||
return s;
|
||||
@@ -139,7 +136,6 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
||||
return schedule(wrapped, executor, delay, unit, cancelled);
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
||||
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
||||
this::runDueTasks, "TaskScheduler");
|
||||
@@ -210,7 +206,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
||||
private final Future<?> check;
|
||||
private final AtomicBoolean cancelled;
|
||||
|
||||
private ScheduledTask(Runnable task, long dueMillis,
|
||||
public ScheduledTask(Runnable task, long dueMillis,
|
||||
Future<?> check, AtomicBoolean cancelled) {
|
||||
this.task = task;
|
||||
this.dueMillis = dueMillis;
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
@@ -112,10 +111,14 @@ public class AndroidUtils {
|
||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static File getLogcatFile(Context ctx) {
|
||||
public static File getTemporaryLogFile(Context ctx) {
|
||||
return new File(ctx.getFilesDir(), STORED_LOGCAT);
|
||||
}
|
||||
|
||||
public static File getPersistentLogDir(Context ctx) {
|
||||
return ctx.getDir("log", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of supported content types for image attachments.
|
||||
*/
|
||||
@@ -135,8 +138,4 @@ public class AndroidUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isUiThread() {
|
||||
return Looper.myLooper() == Looper.getMainLooper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@ import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
@@ -27,6 +31,10 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
context.mock(SharedPreferences.class, "prefs");
|
||||
private final SharedPreferences defaultPrefs =
|
||||
context.mock(SharedPreferences.class, "defaultPrefs");
|
||||
private final PersistentLogManager logManager =
|
||||
context.mock(PersistentLogManager.class);
|
||||
private final FeatureFlags featureFlags =
|
||||
context.mock(FeatureFlags.class);
|
||||
private final DatabaseConfig databaseConfig =
|
||||
context.mock(DatabaseConfig.class);
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
@@ -40,11 +48,12 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File keyDir = new File(testDir, "key");
|
||||
private final File dbDir = new File(testDir, "db");
|
||||
private final File logDir = new File(testDir, "log");
|
||||
|
||||
private AndroidAccountManager accountManager;
|
||||
|
||||
public AndroidAccountManagerTest() {
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
app = context.mock(Application.class);
|
||||
applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.dataDir = testDir.getAbsolutePath();
|
||||
@@ -61,7 +70,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
will(returnValue(app));
|
||||
}});
|
||||
accountManager = new AndroidAccountManager(databaseConfig, crypto,
|
||||
identityManager, prefs, app) {
|
||||
identityManager, prefs, logManager, featureFlags, app) {
|
||||
@Override
|
||||
SharedPreferences getDefaultSharedPreferences() {
|
||||
return defaultPrefs;
|
||||
@@ -109,10 +118,17 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
will(returnValue(cacheDir));
|
||||
oneOf(app).getExternalCacheDir();
|
||||
will(returnValue(externalCacheDir));
|
||||
oneOf(featureFlags).shouldEnablePersistentLogs();
|
||||
will(returnValue(true));
|
||||
oneOf(app).getDir("log", MODE_PRIVATE);
|
||||
will(returnValue(logDir));
|
||||
oneOf(logManager).addLogHandler(with(logDir),
|
||||
with(any(Logger.class)));
|
||||
}});
|
||||
|
||||
assertTrue(dbDir.mkdirs());
|
||||
assertTrue(keyDir.mkdirs());
|
||||
assertTrue(logDir.mkdirs());
|
||||
assertTrue(codeCacheDir.mkdirs());
|
||||
assertTrue(codeCacheFile.createNewFile());
|
||||
assertTrue(libDir.mkdirs());
|
||||
@@ -130,6 +146,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
|
||||
assertFalse(dbDir.exists());
|
||||
assertFalse(keyDir.exists());
|
||||
assertFalse(logDir.exists());
|
||||
assertTrue(codeCacheDir.exists());
|
||||
assertTrue(codeCacheFile.exists());
|
||||
assertTrue(libDir.exists());
|
||||
|
||||
@@ -87,8 +87,8 @@ dependencyVerification {
|
||||
'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: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:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
|
||||
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
|
||||
'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:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
|
||||
|
||||
@@ -5,16 +5,15 @@ targetCompatibility = 1.8
|
||||
apply plugin: 'ru.vyarus.animalsniffer'
|
||||
apply plugin: 'witness'
|
||||
apply from: 'witness.gradle'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.dagger:dagger:$dagger_version"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
|
||||
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$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'
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
public interface Cancellable {
|
||||
|
||||
void cancel();
|
||||
}
|
||||
@@ -11,11 +11,5 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableMailbox();
|
||||
|
||||
boolean shouldEnablePrivateGroupsInCore();
|
||||
|
||||
boolean shouldEnableForumsInCore();
|
||||
|
||||
boolean shouldEnableBlogsInCore();
|
||||
boolean shouldEnablePersistentLogs();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||
|
||||
@NotNullByDefault
|
||||
public abstract class StringMap extends Hashtable<String, String> {
|
||||
|
||||
protected StringMap(Map<String, String> m) {
|
||||
@@ -60,29 +62,17 @@ public abstract class StringMap extends Hashtable<String, String> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public int[] getIntArray(String key) {
|
||||
public byte[] getBytes(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 fromHexString(s);
|
||||
} catch (IllegalArgumentException 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, ","));
|
||||
public void putBytes(String key, byte[] value) {
|
||||
put(key, toHexString(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class UniqueId extends Bytes {
|
||||
public abstract class UniqueId extends Bytes {
|
||||
|
||||
/**
|
||||
* The length of a unique identifier in bytes.
|
||||
*/
|
||||
public static final int LENGTH = 32;
|
||||
|
||||
public UniqueId(byte[] id) {
|
||||
protected UniqueId(byte[] id) {
|
||||
super(id);
|
||||
if (id.length != LENGTH) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
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.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -20,7 +18,6 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -126,19 +123,6 @@ public interface ClientHelper {
|
||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
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
|
||||
* group.
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ConnectionManager {
|
||||
@@ -17,17 +16,6 @@ public interface ConnectionManager {
|
||||
*/
|
||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
|
||||
|
||||
/**
|
||||
* Manages an incoming connection from a contact via a mailbox.
|
||||
* <p>
|
||||
* This method does not mark the tag as recognised until after the data
|
||||
* has been read from the {@link TransportConnectionReader}, at which
|
||||
* point the {@link TagController} is called to decide whether the tag
|
||||
* should be marked as recognised.
|
||||
*/
|
||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r,
|
||||
TagController c);
|
||||
|
||||
/**
|
||||
* Manages an incoming connection from a contact over a duplex transport.
|
||||
*/
|
||||
@@ -46,14 +34,6 @@ public interface ConnectionManager {
|
||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w);
|
||||
|
||||
/**
|
||||
* Manages an outgoing connection to a contact via a mailbox. The IDs of
|
||||
* any messages sent or acked are added to the given
|
||||
* {@link OutgoingSessionRecord}.
|
||||
*/
|
||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
|
||||
|
||||
/**
|
||||
* Manages an outgoing connection to a contact over a duplex transport.
|
||||
*/
|
||||
@@ -66,21 +46,4 @@ public interface ConnectionManager {
|
||||
*/
|
||||
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
||||
DuplexTransportConnection d);
|
||||
|
||||
/**
|
||||
* An interface for controlling whether a tag should be marked as
|
||||
* recognised.
|
||||
*/
|
||||
interface TagController {
|
||||
/**
|
||||
* This method is only called if a tag was read from the corresponding
|
||||
* {@link TransportConnectionReader} and recognised.
|
||||
*
|
||||
* @param exception True if an exception was thrown while reading from
|
||||
* the {@link TransportConnectionReader}, after successfully reading
|
||||
* and recognising the tag.
|
||||
* @return True if the tag should be marked as recognised.
|
||||
*/
|
||||
boolean shouldMarkTagAsRecognised(boolean exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,32 +107,6 @@ public interface ContactManager {
|
||||
*/
|
||||
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
|
||||
* alias, adds it to the database and returns it.
|
||||
@@ -166,24 +140,11 @@ public interface ContactManager {
|
||||
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||
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}.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
@@ -16,4 +17,9 @@ public class PendingContactId extends UniqueId {
|
||||
public PendingContactId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof PendingContactId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -11,8 +10,6 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface CryptoComponent {
|
||||
|
||||
UniqueId generateUniqueId();
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
@@ -175,11 +172,9 @@ public interface CryptoComponent {
|
||||
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
|
||||
* Encode the onion/hidden service address given its public key. As
|
||||
* specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||
*/
|
||||
String encodeOnion(byte[] publicKey);
|
||||
String encodeOnionAddress(byte[] publicKey);
|
||||
|
||||
}
|
||||
|
||||
@@ -33,18 +33,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Encapsulates the database implementation and exposes high-level operations
|
||||
* 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
|
||||
public interface DatabaseComponent extends TransactionManager {
|
||||
|
||||
@@ -126,11 +119,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
TransportKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
|
||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -156,18 +154,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any messages to send to the given contact
|
||||
* over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsMessagesToSend(Transaction txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
@@ -207,15 +193,26 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a batch of messages for the given contact, for transmission 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. Returns null if there are no sendable messages that would fit
|
||||
* in the given capacity.
|
||||
* Returns a batch of messages for the given contact, with a total length
|
||||
* less than or equal to the given length, for transmission over a
|
||||
* transport with the given maximum latency. Returns null if there are no
|
||||
* sendable messages that fit in the given length.
|
||||
*/
|
||||
@Nullable
|
||||
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
|
||||
@@ -235,16 +232,15 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a batch of messages for the given contact, for transmission over
|
||||
* a transport with the given maximum latency. Only messages that have been
|
||||
* requested by the contact are returned. The total length of the messages,
|
||||
* including record headers, will be no more than the given capacity.
|
||||
* Returns null if there are no sendable messages that have been requested
|
||||
* by the contact and would fit in the given capacity.
|
||||
* Returns a batch of messages for the given contact, with a total length
|
||||
* less than or equal to the given length, for transmission over a
|
||||
* transport with the given maximum latency. Only messages that have been
|
||||
* requested by the contact are returned. Returns null if there are no
|
||||
* sendable messages that fit in the given length.
|
||||
*/
|
||||
@Nullable
|
||||
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.
|
||||
@@ -283,13 +279,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
Group getGroup(Transaction txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the group containing the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
GroupId getGroupId(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given group.
|
||||
* <p/>
|
||||
@@ -355,30 +344,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||
Metadata query) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all messages received from the given contact that
|
||||
* need to be acknowledged.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c)
|
||||
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.
|
||||
* <p/>
|
||||
@@ -495,42 +460,17 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||
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.
|
||||
* <p/>
|
||||
* Read-only if {@code markAsSent} is false.
|
||||
*
|
||||
* @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
|
||||
* given contact.
|
||||
* <p>
|
||||
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, long)}
|
||||
* this method may return messages that have already been sent and are
|
||||
* not yet due for retransmission.
|
||||
* given contact, together with their raw lengths. This may include
|
||||
* messages that have already been sent and are not yet due for
|
||||
* retransmission.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getUnackedMessagesToSend(Transaction txn,
|
||||
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
||||
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
|
||||
* eligible to be sent to the given contact. This may include messages
|
||||
@@ -550,18 +490,15 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact over a transport with
|
||||
* the given latency.
|
||||
* <p>
|
||||
* The returned value may be zero if a message is due to be sent
|
||||
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||
* no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
@@ -703,13 +640,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||
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
|
||||
* start the message's cleanup timer.
|
||||
@@ -756,13 +686,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
void setMessageState(Transaction txn, MessageId m, MessageState state)
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -18,10 +18,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
* submitted, tasks are not run concurrently, and submitting a task will never
|
||||
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
|
||||
* 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
|
||||
@Target({FIELD, METHOD, PARAMETER})
|
||||
|
||||
@@ -45,9 +45,6 @@ public class Transaction {
|
||||
/**
|
||||
* Attaches an event to be broadcast when the transaction has been
|
||||
* 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) {
|
||||
if (actions == null) actions = new ArrayList<>();
|
||||
@@ -57,9 +54,6 @@ public class Transaction {
|
||||
/**
|
||||
* Attaches a task to be executed when the transaction has been
|
||||
* 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) {
|
||||
if (actions == null) actions = new ArrayList<>();
|
||||
|
||||
@@ -1,95 +1,51 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
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
|
||||
public interface TransactionManager {
|
||||
|
||||
/**
|
||||
* Starts a new transaction and returns an object representing it. This
|
||||
* method acquires the database lock, which is held until
|
||||
* {@link #endTransaction(Transaction)} is called.
|
||||
* Starts a new transaction and returns an object representing it.
|
||||
* <p/>
|
||||
* 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,
|
||||
* in which case the database lock can be shared with other read-only
|
||||
* transactions.
|
||||
* @param readOnly true if the transaction will only be used for reading.
|
||||
*/
|
||||
Transaction startTransaction(boolean readOnly) throws DbException;
|
||||
|
||||
/**
|
||||
* Commits a transaction to the database.
|
||||
* {@link #endTransaction(Transaction)} must be called to release the
|
||||
* database lock.
|
||||
*/
|
||||
void commitTransaction(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Ends a transaction. If the transaction has not been committed by
|
||||
* calling {@link #commitTransaction(Transaction)}, it is aborted and the
|
||||
* database lock is released.
|
||||
* <p>
|
||||
* 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.
|
||||
* Ends a transaction. If the transaction has not been committed,
|
||||
* it will be aborted. If the transaction has been committed,
|
||||
* any events attached to the transaction are broadcast.
|
||||
* The database lock will be released in either case.
|
||||
*/
|
||||
void endTransaction(Transaction txn);
|
||||
|
||||
/**
|
||||
* Runs the given task within a transaction. The database lock is held
|
||||
* 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.
|
||||
* Runs the given task within a transaction.
|
||||
*/
|
||||
<E extends Exception> void transaction(boolean readOnly,
|
||||
DbRunnable<E> task) throws DbException, E;
|
||||
|
||||
/**
|
||||
* Runs the given task within a transaction and returns the result of the
|
||||
* task. The database lock is held 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.
|
||||
* task.
|
||||
*/
|
||||
<R, E extends Exception> R transactionWithResult(boolean readOnly,
|
||||
DbCallable<R, E> task) throws DbException, E;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
* task, which may be null.
|
||||
*/
|
||||
@Nullable
|
||||
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,
|
||||
|
||||
@@ -21,4 +21,9 @@ public class AuthorId extends UniqueId {
|
||||
public AuthorId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof AuthorId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,8 @@ public interface LifecycleManager {
|
||||
*/
|
||||
enum LifecycleState {
|
||||
|
||||
CREATED,
|
||||
STARTING,
|
||||
MIGRATING_DATABASE,
|
||||
COMPACTING_DATABASE,
|
||||
STARTING_SERVICES,
|
||||
RUNNING,
|
||||
STOPPING,
|
||||
STOPPED;
|
||||
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
|
||||
RUNNING, STOPPING;
|
||||
|
||||
public boolean isAfter(LifecycleState state) {
|
||||
return ordinal() > state.ordinal();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.bramble.api.logging;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PersistentLogManager {
|
||||
|
||||
/**
|
||||
* The namespace of the (@link Settings) where the log key is stored.
|
||||
*/
|
||||
String LOG_SETTINGS_NAMESPACE = "log";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the log key is stored.
|
||||
*/
|
||||
String LOG_KEY_KEY = "logKey";
|
||||
|
||||
/**
|
||||
* Creates and returns a persistent log handler that stores its logs in
|
||||
* the given directory.
|
||||
*/
|
||||
Handler createLogHandler(File dir) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a persistent log handler that stores its logs in the given
|
||||
* directory and adds the handler to the given logger, replacing any
|
||||
* existing persistent log handler.
|
||||
*/
|
||||
void addLogHandler(File dir, Logger logger) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a {@link Scanner} for reading the persistent log entries stored
|
||||
* in the given directory.
|
||||
*
|
||||
* @param old True if the previous session's log should be loaded, or false
|
||||
* if the current session's log should be loaded
|
||||
*/
|
||||
Scanner getPersistentLog(File dir, boolean old) throws IOException;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public class InvalidMailboxIdException extends Exception {
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
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 transport ID of the mailbox plugin.
|
||||
*/
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
||||
/**
|
||||
* The constant returned by
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||
* when the server is too old to support our major version.
|
||||
*/
|
||||
int API_SERVER_TOO_OLD = -1;
|
||||
|
||||
/**
|
||||
* The constant returned by
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||
* when we as a client are too old to support the server's major version.
|
||||
*/
|
||||
int API_CLIENT_TOO_OLD = -2;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* The maximum latency of the mailbox transport in milliseconds.
|
||||
*/
|
||||
long MAX_LATENCY = DAYS.toMillis(14);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotation for injecting the {@link File directory} where the Mailbox plugin
|
||||
* should store its state.
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({FIELD, METHOD, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
public @interface MailboxDirectory {
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||
|
||||
@NotNullByDefault
|
||||
public class MailboxHelper {
|
||||
|
||||
/**
|
||||
* Returns the highest major version that both client and server support
|
||||
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
|
||||
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
|
||||
*/
|
||||
public static int getHighestCommonMajorVersion(
|
||||
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||
TreeSet<Integer> clientVersions = new TreeSet<>();
|
||||
for (MailboxVersion version : client) {
|
||||
clientVersions.add(version.getMajor());
|
||||
}
|
||||
TreeSet<Integer> serverVersions = new TreeSet<>();
|
||||
for (MailboxVersion version : server) {
|
||||
serverVersions.add(version.getMajor());
|
||||
}
|
||||
for (int clientVersion : clientVersions.descendingSet()) {
|
||||
if (serverVersions.contains(clientVersion)) return clientVersion;
|
||||
}
|
||||
if (clientVersions.last() < serverVersions.last()) {
|
||||
return API_CLIENT_TOO_OLD;
|
||||
}
|
||||
return API_SERVER_TOO_OLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a client and server with the given API versions can
|
||||
* communicate with each other (ie, have any major API versions in common).
|
||||
*/
|
||||
public static boolean isClientCompatibleWithServer(
|
||||
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||
int common = getHighestCommonMajorVersion(client, server);
|
||||
return common != API_CLIENT_TOO_OLD && common != API_SERVER_TOO_OLD;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
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 (FormatException 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -1,98 +1,32 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxProperties {
|
||||
|
||||
private final String onion;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final String onionAddress, 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 onion, MailboxAuthToken authToken,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
this.onion = onion;
|
||||
public MailboxProperties(String onionAddress, String authToken,
|
||||
boolean owner) {
|
||||
this.onionAddress = onionAddress;
|
||||
this.authToken = authToken;
|
||||
this.owner = true;
|
||||
this.serverSupports = serverSupports;
|
||||
this.inboxId = null;
|
||||
this.outboxId = null;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for properties used by a contact of the mailbox's owner.
|
||||
*/
|
||||
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
||||
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
|
||||
MailboxFolderId outboxId) {
|
||||
this.onion = onion;
|
||||
this.authToken = authToken;
|
||||
this.owner = false;
|
||||
this.serverSupports = serverSupports;
|
||||
this.inboxId = inboxId;
|
||||
this.outboxId = outboxId;
|
||||
public String getOnionAddress() {
|
||||
return onionAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the onion address of the mailbox, excluding the .onion suffix.
|
||||
*/
|
||||
public String getOnion() {
|
||||
return onion;
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
public String 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof MailboxProperties) {
|
||||
MailboxProperties m = (MailboxProperties) o;
|
||||
return owner == m.owner &&
|
||||
onion.equals(m.onion) &&
|
||||
authToken.equals(m.authToken) &&
|
||||
NullSafety.equals(inboxId, m.inboxId) &&
|
||||
NullSafety.equals(outboxId, m.outboxId) &&
|
||||
serverSupports.equals(m.serverSupports);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return authToken.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
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;
|
||||
@@ -28,46 +16,11 @@ public interface MailboxSettingsManager {
|
||||
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,
|
||||
List<MailboxVersion> versions) 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
|
||||
*/
|
||||
void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Called when the mailbox is unpaired.
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void mailboxUnpaired(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Called when we receive our mailbox's server-supported API versions.
|
||||
* This happens whenever we successfully check the connectivity of
|
||||
* our mailbox, so this hook may be called frequently.
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void serverSupportedVersionsReceived(Transaction txn,
|
||||
List<MailboxVersion> serverSupports) throws DbException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,20 @@ package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
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;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxStatus {
|
||||
|
||||
private final long lastAttempt, lastSuccess;
|
||||
private final int attemptsSinceSuccess;
|
||||
private final List<MailboxVersion> serverSupports;
|
||||
|
||||
public MailboxStatus(long lastAttempt, long lastSuccess,
|
||||
int attemptsSinceSuccess,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
int attemptsSinceSuccess) {
|
||||
this.lastAttempt = lastAttempt;
|
||||
this.lastSuccess = lastSuccess;
|
||||
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
||||
this.serverSupports = serverSupports;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,28 +56,4 @@ public class MailboxStatus {
|
||||
public int getAttemptsSinceSuccess() {
|
||||
return attemptsSinceSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mailbox's supported API versions.
|
||||
*/
|
||||
public List<MailboxVersion> getServerSupports() {
|
||||
return serverSupports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a positive integer if the mailbox is compatible. Same result as
|
||||
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}.
|
||||
*/
|
||||
public int getMailboxCompatibility() {
|
||||
return getHighestCommonMajorVersion(CLIENT_SUPPORTS, serverSupports);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +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.
|
||||
* <p>
|
||||
* The required properties are {@link #PROP_KEY_ONION},
|
||||
* {@link #PROP_KEY_AUTHTOKEN}, {@link #PROP_KEY_INBOXID} and
|
||||
* {@link #PROP_KEY_OUTBOXID}.
|
||||
*/
|
||||
int PROP_COUNT = 4;
|
||||
|
||||
/**
|
||||
* The onion address of the mailbox, excluding the .onion suffix.
|
||||
*/
|
||||
String PROP_KEY_ONION = "onion";
|
||||
|
||||
/**
|
||||
* A bearer token for accessing the mailbox (64 hex digits).
|
||||
*/
|
||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||
|
||||
/**
|
||||
* A folder ID for downloading messages (64 hex digits).
|
||||
*/
|
||||
String PROP_KEY_INBOXID = "inboxId";
|
||||
|
||||
/**
|
||||
* A folder ID for uploading messages (64 hex digits).
|
||||
*/
|
||||
String PROP_KEY_OUTBOXID = "outboxId";
|
||||
|
||||
/**
|
||||
* Length of the {@link #PROP_KEY_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";
|
||||
|
||||
/**
|
||||
* Key in the client's local group for storing the serverSupports list that
|
||||
* was last sent out, if any.
|
||||
*/
|
||||
String GROUP_KEY_SENT_SERVER_SUPPORTS = "sentServerSupports";
|
||||
|
||||
/**
|
||||
* Returns the latest {@link MailboxUpdate} sent to the given contact.
|
||||
* <p>
|
||||
* If we have our own mailbox then the update will be a
|
||||
* {@link MailboxUpdateWithMailbox} containing the
|
||||
* {@link MailboxProperties} the contact should use for communicating with
|
||||
* our mailbox.
|
||||
*/
|
||||
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the latest {@link MailboxUpdate} received from the given
|
||||
* contact, or null if no update has been received.
|
||||
* <p>
|
||||
* If the contact has a mailbox then the update will be a
|
||||
* {@link MailboxUpdateWithMailbox} containing the
|
||||
* {@link MailboxProperties} we should use for communicating with the
|
||||
* contact's mailbox.
|
||||
*/
|
||||
@Nullable
|
||||
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +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.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a mailbox is paired.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxPairedEvent extends Event {
|
||||
|
||||
private final MailboxProperties properties;
|
||||
private final Map<ContactId, MailboxUpdateWithMailbox> localUpdates;
|
||||
|
||||
public MailboxPairedEvent(MailboxProperties properties,
|
||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
|
||||
this.properties = properties;
|
||||
this.localUpdates = localUpdates;
|
||||
}
|
||||
|
||||
public MailboxProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Map<ContactId, MailboxUpdateWithMailbox> getLocalUpdates() {
|
||||
return localUpdates;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -1,28 +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 java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a mailbox is unpaired.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxUnpairedEvent extends Event {
|
||||
|
||||
private final Map<ContactId, MailboxUpdate> localUpdates;
|
||||
|
||||
public MailboxUnpairedEvent(Map<ContactId, MailboxUpdate> localUpdates) {
|
||||
this.localUpdates = localUpdates;
|
||||
}
|
||||
|
||||
public Map<ContactId, MailboxUpdate> getLocalUpdates() {
|
||||
return localUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the first mailbox update is sent to a
|
||||
* newly added contact, which happens in the same transaction in which the
|
||||
* contact is added.
|
||||
* <p>
|
||||
* This event is not broadcast when the first mailbox update is sent to an
|
||||
* existing contact when setting up the
|
||||
* {@link MailboxUpdateManager mailbox update client}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxUpdateSentToNewContactEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final MailboxUpdate mailboxUpdate;
|
||||
|
||||
public MailboxUpdateSentToNewContactEvent(ContactId contactId,
|
||||
MailboxUpdate mailboxUpdate) {
|
||||
this.contactId = contactId;
|
||||
this.mailboxUpdate = mailboxUpdate;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public MailboxUpdate getMailboxUpdate() {
|
||||
return mailboxUpdate;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
public interface TorConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||
@@ -12,9 +10,8 @@ public interface TorConstants {
|
||||
int DEFAULT_SOCKS_PORT = 59050;
|
||||
int DEFAULT_CONTROL_PORT = 59051;
|
||||
|
||||
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
|
||||
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
|
||||
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
|
||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
||||
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
||||
|
||||
// Local settings (not shared with contacts)
|
||||
String PREF_TOR_NETWORK = "network2";
|
||||
|
||||
@@ -12,6 +12,4 @@ public interface RecordWriter {
|
||||
void flush() throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
|
||||
long getBytesWritten();
|
||||
}
|
||||
|
||||
@@ -8,11 +8,24 @@ import java.io.File;
|
||||
@NotNullByDefault
|
||||
public interface DevConfig {
|
||||
|
||||
/**
|
||||
* Returns the public key for encrypting feedback and crash reports.
|
||||
*/
|
||||
PublicKey getDevPublicKey();
|
||||
|
||||
/**
|
||||
* Returns the onion address for submitting feedback and crash reports.
|
||||
*/
|
||||
String getDevOnionAddress();
|
||||
|
||||
/**
|
||||
* Returns the directory for storing unsent feedback and crash reports.
|
||||
*/
|
||||
File getReportDir();
|
||||
|
||||
File getLogcatFile();
|
||||
/**
|
||||
* Returns the temporary file for passing the encrypted app log from the
|
||||
* main process to the crash reporter process.
|
||||
*/
|
||||
File getTemporaryLogFile();
|
||||
}
|
||||
|
||||
@@ -20,4 +20,9 @@ public class GroupId extends UniqueId {
|
||||
public GroupId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof GroupId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,9 @@ public class MessageId extends UniqueId {
|
||||
public MessageId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof MessageId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A container 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.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class OutgoingSessionRecord {
|
||||
|
||||
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
|
||||
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
|
||||
|
||||
public void onAckSent(Collection<MessageId> acked) {
|
||||
ackedIds.addAll(acked);
|
||||
}
|
||||
|
||||
public void onMessageSent(MessageId sent) {
|
||||
sentIds.add(sent);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getAckedIds() {
|
||||
return ackedIds;
|
||||
}
|
||||
|
||||
public Collection<MessageId> getSentIds() {
|
||||
return sentIds;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,4 @@ public interface SyncRecordWriter {
|
||||
void writePriority(Priority p) throws IOException;
|
||||
|
||||
void flush() throws IOException;
|
||||
|
||||
long getBytesWritten();
|
||||
}
|
||||
|
||||
@@ -12,30 +12,12 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface SyncSessionFactory {
|
||||
|
||||
/**
|
||||
* Creates a session for receiving data from a contact.
|
||||
*/
|
||||
SyncSession createIncomingSession(ContactId c, InputStream in,
|
||||
PriorityHandler handler);
|
||||
|
||||
/**
|
||||
* Creates a session for sending data to a contact over a simplex transport.
|
||||
*
|
||||
* @param eager True if messages should be sent eagerly, ie regardless of
|
||||
* whether they're due for retransmission.
|
||||
*/
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, boolean eager, StreamWriter streamWriter);
|
||||
|
||||
/**
|
||||
* Creates a session for sending data to a contact via a mailbox. The IDs
|
||||
* of any messages sent or acked will be added to the given
|
||||
* {@link OutgoingSessionRecord}.
|
||||
*/
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, StreamWriter streamWriter,
|
||||
OutgoingSessionRecord sessionRecord);
|
||||
|
||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||
@Nullable Priority priority);
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.sync.event;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class GroupVisibilityUpdatedEvent extends Event {
|
||||
|
||||
private final Visibility visibility;
|
||||
private final Collection<ContactId> affected;
|
||||
|
||||
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
||||
Collection<ContactId> affected) {
|
||||
this.visibility = visibility;
|
||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
||||
this.affected = affected;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contacts affected by the update.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package org.briarproject.bramble.api.sync.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
@@ -19,32 +14,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
public class MessageSharedEvent extends Event {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final GroupId groupId;
|
||||
private final Map<ContactId, Boolean> groupVisibility;
|
||||
|
||||
public MessageSharedEvent(MessageId message, GroupId groupId,
|
||||
Map<ContactId, Boolean> groupVisibility) {
|
||||
public MessageSharedEvent(MessageId message) {
|
||||
this.messageId = message;
|
||||
this.groupId = groupId;
|
||||
this.groupVisibility = groupVisibility;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all contacts for which the visibility of the
|
||||
* message's group is either {@link Visibility#SHARED shared} or
|
||||
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
|
||||
* group is {@link Visibility#SHARED shared} or false if the group is
|
||||
* {@link Visibility#VISIBLE visible}.
|
||||
*/
|
||||
public Map<ContactId, Boolean> getGroupVisibility() {
|
||||
return groupVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -17,8 +16,6 @@ public interface TaskScheduler {
|
||||
* <p>
|
||||
* If the platform supports wake locks, a wake lock will be held while
|
||||
* submitting and running the task.
|
||||
*
|
||||
* @return A {@link Cancellable} for cancelling the task.
|
||||
*/
|
||||
Cancellable schedule(Runnable task, Executor executor, long delay,
|
||||
TimeUnit unit);
|
||||
@@ -30,11 +27,17 @@ public interface TaskScheduler {
|
||||
* <p>
|
||||
* If the platform supports wake locks, a wake lock will be held while
|
||||
* submitting and running the task.
|
||||
*
|
||||
* @return A {@link Cancellable} for cancelling all future executions of
|
||||
* the task.
|
||||
*/
|
||||
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,25 +113,9 @@ public interface KeyManager {
|
||||
/**
|
||||
* 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. Marks the tag as recognised and updates the reordering
|
||||
* window.
|
||||
* unexpected.
|
||||
*/
|
||||
@Nullable
|
||||
StreamContext getStreamContext(TransportId t, byte[] tag)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class IoUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(File f) {
|
||||
private static void delete(File f) {
|
||||
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
@@ -57,4 +60,13 @@ public class LogUtils {
|
||||
String type) {
|
||||
logger.log(level, type + " " + f.getAbsolutePath() + " " + f.length());
|
||||
}
|
||||
|
||||
public static String formatLog(Formatter formatter,
|
||||
Collection<LogRecord> logRecords) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (LogRecord record : logRecords) {
|
||||
sb.append(formatter.format(record)).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class NetworkUtils {
|
||||
// Despite what the docs say, the return value can be null
|
||||
//noinspection ConstantConditions
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException | NullPointerException e) {
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -96,10 +95,10 @@ public class StringUtils {
|
||||
/**
|
||||
* Converts the given hex string to a byte array.
|
||||
*/
|
||||
public static byte[] fromHexString(String hex) throws FormatException {
|
||||
public static byte[] fromHexString(String hex) {
|
||||
int len = hex.length();
|
||||
if (len % 2 != 0)
|
||||
throw new FormatException();
|
||||
throw new IllegalArgumentException("Not a hex string");
|
||||
byte[] bytes = new byte[len / 2];
|
||||
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
||||
int high = hexDigitToInt(hex.charAt(i));
|
||||
@@ -109,11 +108,11 @@ public class StringUtils {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static int hexDigitToInt(char c) throws FormatException {
|
||||
private static int hexDigitToInt(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
throw new FormatException();
|
||||
throw new IllegalArgumentException("Not a hex digit: " + c);
|
||||
}
|
||||
|
||||
public static String trim(String s) {
|
||||
@@ -131,13 +130,13 @@ public class StringUtils {
|
||||
return MAC.matcher(mac).matches();
|
||||
}
|
||||
|
||||
public static byte[] macToBytes(String mac) throws FormatException {
|
||||
if (!MAC.matcher(mac).matches()) throw new FormatException();
|
||||
public static byte[] macToBytes(String mac) {
|
||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||
return fromHexString(mac.replaceAll(":", ""));
|
||||
}
|
||||
|
||||
public static String macToString(byte[] mac) throws FormatException {
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
public static String macToString(byte[] mac) {
|
||||
if (mac.length != 6) throw new IllegalArgumentException();
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (byte b : mac) {
|
||||
if (s.length() > 0) s.append(':');
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MailboxHelperTest {
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
@Test
|
||||
public void testGetHighestCommonMajorVersion() {
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(1, 2), v(2, 3, 4)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2, 3, 4), v(2)));
|
||||
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2, 3, 4)));
|
||||
|
||||
assertEquals(API_CLIENT_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(2), v(3, 4)));
|
||||
assertEquals(API_CLIENT_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(2), v(1, 3)));
|
||||
assertEquals(API_SERVER_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(3, 4, 5), v(2)));
|
||||
assertEquals(API_SERVER_TOO_OLD,
|
||||
getHighestCommonMajorVersion(v(1, 3), v(2)));
|
||||
}
|
||||
|
||||
private List<MailboxVersion> v(int... ints) {
|
||||
List<MailboxVersion> versions = new ArrayList<>(ints.length);
|
||||
for (int v : ints) {
|
||||
// minor versions should not matter
|
||||
versions.add(new MailboxVersion(v, random.nextInt(42)));
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +1,17 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public abstract class BrambleTestCase {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BrambleTestCase.class.getName());
|
||||
|
||||
@Nullable
|
||||
protected volatile Throwable exceptionInBackgroundThread = null;
|
||||
|
||||
public BrambleTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||
LOG.log(WARNING, "Caught unhandled exception", throwable);
|
||||
exceptionInBackgroundThread = throwable;
|
||||
throwable.printStackTrace();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,10 @@ import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
||||
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.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
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.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
@@ -35,12 +25,7 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -50,9 +35,6 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
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_SIGNATURE_PUBLIC_KEY_BYTES;
|
||||
@@ -63,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.SyncConstants.MAX_GROUP_DESCRIPTOR_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;
|
||||
|
||||
public class TestUtils {
|
||||
@@ -228,37 +209,6 @@ public class TestUtils {
|
||||
getAgreementPublicKey(), verified);
|
||||
}
|
||||
|
||||
public static MailboxProperties getMailboxProperties(boolean owner,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
String onion = getRandomString(56);
|
||||
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
||||
if (owner) {
|
||||
return new MailboxProperties(onion, authToken, serverSupports);
|
||||
}
|
||||
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
||||
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
||||
return new MailboxProperties(onion, 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) {
|
||||
int size = samples.size();
|
||||
if (size == 0) throw new IllegalArgumentException();
|
||||
@@ -293,68 +243,9 @@ public class TestUtils {
|
||||
return Math.sqrt(getVariance(samples));
|
||||
}
|
||||
|
||||
public static boolean isOptionalTestEnabled(Class<?> testClass) {
|
||||
public static boolean isOptionalTestEnabled(Class testClass) {
|
||||
String optionalTests = System.getenv("OPTIONAL_TESTS");
|
||||
return optionalTests != null &&
|
||||
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;
|
||||
}
|
||||
|
||||
public static <E extends Event> E getEvent(Transaction txn,
|
||||
Class<E> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return eventClass.cast(event);
|
||||
}
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static boolean isCryptoStrengthUnlimited() {
|
||||
try {
|
||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
||||
== Integer.MAX_VALUE;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'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:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
||||
|
||||
@@ -7,21 +7,15 @@ apply plugin: 'idea'
|
||||
apply plugin: 'witness'
|
||||
apply from: 'witness.gradle'
|
||||
apply from: '../dagger.gradle'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
|
||||
//noinspection GradleDependency
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
|
||||
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||
implementation 'org.briarproject:jtorctl:0.5'
|
||||
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
implementation 'org.briarproject:jtorctl:0.3'
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -31,8 +25,7 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmock_version"
|
||||
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||
@@ -29,8 +28,6 @@ public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(LifecycleModule.EagerSingletons init);
|
||||
|
||||
void inject(MailboxModule.EagerSingletons init);
|
||||
|
||||
void inject(PluginModule.EagerSingletons init);
|
||||
|
||||
void inject(PropertiesModule.EagerSingletons init);
|
||||
@@ -54,7 +51,6 @@ public interface BrambleCoreEagerSingletons {
|
||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new MailboxModule.EagerSingletons());
|
||||
c.inject(new RendezvousModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.io.IoModule;
|
||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.logging.LoggingModule;
|
||||
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
@@ -44,6 +45,7 @@ import dagger.Module;
|
||||
IoModule.class,
|
||||
KeyAgreementModule.class,
|
||||
LifecycleModule.class,
|
||||
LoggingModule.class,
|
||||
MailboxModule.class,
|
||||
PluginModule.class,
|
||||
PropertiesModule.class,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.account;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.account.AccountManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.DecryptionException;
|
||||
@@ -210,13 +209,7 @@ class AccountManagerImpl implements AccountManager {
|
||||
LOG.warning("Failed to load encrypted database key");
|
||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||
}
|
||||
byte[] ciphertext;
|
||||
try {
|
||||
ciphertext = fromHexString(hex);
|
||||
} catch (FormatException e) {
|
||||
LOG.warning("Encrypted database key has invalid format");
|
||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||
}
|
||||
byte[] ciphertext = fromHexString(hex);
|
||||
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||
keyStrengthener);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
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.identity.Author;
|
||||
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.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -36,33 +29,23 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.sort;
|
||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||
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_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_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
@@ -416,70 +399,6 @@ class ClientHelperImpl implements ClientHelper {
|
||||
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);
|
||||
MailboxProperties props = new MailboxProperties(onion,
|
||||
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()));
|
||||
}
|
||||
// Sort the list of versions for easier comparison
|
||||
sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException {
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
byte[] readTag(InputStream in) throws IOException {
|
||||
private byte[] readTag(InputStream in) throws IOException {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
read(in, tag);
|
||||
return tag;
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
@@ -68,15 +67,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionReader r) {
|
||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, t, r, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageIncomingConnection(TransportId t,
|
||||
TransportConnectionReader r, TagController c) {
|
||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, t, r, c));
|
||||
syncSessionFactory, transportPropertyManager, t, r));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,16 +92,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionWriter w) {
|
||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, c, t, w, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageOutgoingConnection(ContactId c, TransportId t,
|
||||
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
|
||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
syncSessionFactory, transportPropertyManager, c, t, w,
|
||||
sessionRecord));
|
||||
syncSessionFactory, transportPropertyManager, c, t, w));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.briarproject.bramble.connection;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -17,8 +15,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@@ -27,8 +23,6 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
|
||||
private final TransportId transportId;
|
||||
private final TransportConnectionReader reader;
|
||||
@Nullable
|
||||
private final TagController tagController;
|
||||
|
||||
IncomingSimplexSyncConnection(KeyManager keyManager,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
@@ -36,50 +30,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriterFactory streamWriterFactory,
|
||||
SyncSessionFactory syncSessionFactory,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
TransportId transportId,
|
||||
TransportConnectionReader reader,
|
||||
@Nullable TagController tagController) {
|
||||
TransportId transportId, TransportConnectionReader reader) {
|
||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||
streamWriterFactory, syncSessionFactory,
|
||||
transportPropertyManager);
|
||||
this.transportId = transportId;
|
||||
this.reader = reader;
|
||||
this.tagController = tagController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Read and recognise the tag
|
||||
byte[] tag;
|
||||
StreamContext ctx;
|
||||
try {
|
||||
tag = readTag(reader.getInputStream());
|
||||
// If we have a tag controller, defer marking the tag as recognised
|
||||
if (tagController == null) {
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} else {
|
||||
ctx = keyManager.getStreamContextOnly(transportId, tag);
|
||||
}
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
StreamContext ctx = recogniseTag(reader, transportId);
|
||||
if (ctx == null) {
|
||||
LOG.info("Unrecognised tag");
|
||||
onError();
|
||||
onError(false);
|
||||
return;
|
||||
}
|
||||
ContactId contactId = ctx.getContactId();
|
||||
if (contactId == null) {
|
||||
LOG.warning("Received rendezvous stream, expected contact");
|
||||
onError(tag);
|
||||
onError(true);
|
||||
return;
|
||||
}
|
||||
if (ctx.isHandshakeMode()) {
|
||||
// TODO: Support handshake mode for contacts
|
||||
LOG.warning("Received handshake tag, expected rotation mode");
|
||||
onError(tag);
|
||||
onError(true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -88,33 +65,15 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
LOG.info("Ignoring priority for simplex connection");
|
||||
// Create and run the incoming session
|
||||
createIncomingSession(ctx, reader, handler).run();
|
||||
// Success
|
||||
markTagAsRecognisedIfRequired(false, tag);
|
||||
reader.dispose(false, true);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onError(tag);
|
||||
onError(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void onError() {
|
||||
disposeOnError(reader, false);
|
||||
}
|
||||
|
||||
private void onError(byte[] tag) {
|
||||
markTagAsRecognisedIfRequired(true, tag);
|
||||
disposeOnError(reader, true);
|
||||
}
|
||||
|
||||
private void markTagAsRecognisedIfRequired(boolean exception, byte[] tag) {
|
||||
if (tagController != null &&
|
||||
tagController.shouldMarkTagAsRecognised(exception)) {
|
||||
try {
|
||||
keyManager.markTagAsRecognised(transportId, tag);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
private void onError(boolean recognised) {
|
||||
disposeOnError(reader, recognised);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
@@ -17,8 +16,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -29,8 +26,6 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final TransportConnectionWriter writer;
|
||||
@Nullable
|
||||
private final OutgoingSessionRecord sessionRecord;
|
||||
|
||||
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
SyncSessionFactory syncSessionFactory,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
ContactId contactId, TransportId transportId,
|
||||
TransportConnectionWriter writer,
|
||||
@Nullable OutgoingSessionRecord sessionRecord) {
|
||||
TransportConnectionWriter writer) {
|
||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||
streamWriterFactory, syncSessionFactory,
|
||||
transportPropertyManager);
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.writer = writer;
|
||||
this.sessionRecord = sessionRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
if (sessionRecord == null) {
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(),
|
||||
w.isLossyAndCheap(), streamWriter);
|
||||
} else {
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
|
||||
sessionRecord);
|
||||
}
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
||||
streamWriter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,40 +121,28 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
|
||||
@Override
|
||||
public String getHandshakeLink() throws DbException {
|
||||
return db.transactionWithResult(true, this::getHandshakeLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandshakeLink(Transaction txn) throws DbException {
|
||||
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
|
||||
KeyPair keyPair = db.transactionWithResult(true,
|
||||
identityManager::getHandshakeKeys);
|
||||
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
|
||||
public PendingContact addPendingContact(String link, String alias)
|
||||
throws DbException, FormatException, GeneralSecurityException {
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
Transaction txn = db.startTransaction(false);
|
||||
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);
|
||||
return p;
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,14 +154,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
@Override
|
||||
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, this::getPendingContacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(
|
||||
Transaction txn)
|
||||
throws DbException {
|
||||
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
|
||||
Collection<PendingContact> pendingContacts =
|
||||
db.transactionWithResult(true, db::getPendingContacts);
|
||||
List<Pair<PendingContact, PendingContactState>> pairs =
|
||||
new ArrayList<>(pendingContacts.size());
|
||||
for (PendingContact p : pendingContacts) {
|
||||
@@ -186,13 +168,7 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
|
||||
@Override
|
||||
public void removePendingContact(PendingContactId p) throws DbException {
|
||||
db.transaction(false, txn -> removePendingContact(txn, p));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePendingContact(Transaction txn, PendingContactId p)
|
||||
throws DbException {
|
||||
db.removePendingContact(txn, p);
|
||||
db.transaction(false, txn -> db.removePendingContact(txn, p));
|
||||
states.remove(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
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.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -42,7 +41,6 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
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_SIGNATURE;
|
||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
|
||||
@@ -56,7 +54,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
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 STORAGE_IV_BYTES = 24; // 196 bits
|
||||
@@ -130,13 +128,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId generateUniqueId() {
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
secureRandom.nextBytes(b);
|
||||
return new UniqueId(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -458,7 +449,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeOnion(byte[] publicKey) {
|
||||
public String encodeOnionAddress(byte[] publicKey) {
|
||||
Digest digest = new SHA3Digest(256);
|
||||
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
|
||||
digest.update(label, 0, label.length);
|
||||
|
||||
@@ -163,11 +163,16 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
|
||||
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
|
||||
boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -207,18 +212,6 @@ interface Database<T> {
|
||||
*/
|
||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
|
||||
boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given pending contact.
|
||||
* <p/>
|
||||
@@ -320,13 +313,6 @@ interface Database<T> {
|
||||
*/
|
||||
Group getGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the group containing the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
GroupId getGroupId(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given group.
|
||||
* <p/>
|
||||
@@ -352,11 +338,8 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all contacts for which the given group's visibility
|
||||
* is either {@link Visibility#SHARED shared} or
|
||||
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
|
||||
* group is {@link Visibility#SHARED shared} or false if the group is
|
||||
* {@link Visibility#VISIBLE visible}.
|
||||
* Returns the IDs of all contacts to which the given group's visibility is
|
||||
* either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -423,12 +406,6 @@ interface Database<T> {
|
||||
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
|
||||
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.
|
||||
* <p/>
|
||||
@@ -519,8 +496,7 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* will be no more than the given capacity.
|
||||
* given contact, up to the given total length.
|
||||
* <p/>
|
||||
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
||||
* does not return messages that have already been sent unless they are
|
||||
@@ -528,20 +504,20 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity,
|
||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
||||
long maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all messages that are eligible to be sent to the
|
||||
* given contact.
|
||||
* given contact, together with their raw lengths.
|
||||
* <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
|
||||
* due for retransmission.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c)
|
||||
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -597,16 +573,13 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact over a transport with
|
||||
* the given latency.
|
||||
* <p>
|
||||
* The returned value may be zero if a message is due to be sent
|
||||
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||
* if no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
@@ -625,14 +598,13 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* of the messages including record headers will be no more than the given
|
||||
* capacity.
|
||||
* given contact and have been requested by the contact, up to the given
|
||||
* total length.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
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.
|
||||
@@ -785,14 +757,6 @@ interface Database<T> {
|
||||
*/
|
||||
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
|
||||
* start the message's cleanup timer.
|
||||
@@ -877,14 +841,12 @@ interface Database<T> {
|
||||
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count, expiry time and max latency of the given
|
||||
* message with respect to the given contact.
|
||||
*
|
||||
* @param maxLatency latency of the transport over which the message was
|
||||
* sent.
|
||||
* Updates the transmission count, expiry time and estimated time of arrival
|
||||
* of the given message with respect to the given contact, using the latency
|
||||
* of the transport over which it was sent.
|
||||
*/
|
||||
void updateRetransmissionData(T txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException;
|
||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
|
||||
@@ -75,6 +75,7 @@ import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -86,7 +87,6 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new MessageAddedEvent(m, null));
|
||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
if (shared) {
|
||||
Map<ContactId, Boolean> visibility =
|
||||
db.getGroupVisibility(txn, m.getGroupId());
|
||||
transaction.attach(new MessageSharedEvent(m.getId(),
|
||||
m.getGroupId(), visibility));
|
||||
}
|
||||
if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
|
||||
}
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
}
|
||||
@@ -347,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.containsAcksToSend(txn, c);
|
||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -378,15 +373,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.containsIdentity(txn, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.containsMessagesToSend(txn, c, maxLatency, eager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Transaction transaction,
|
||||
PendingContactId p) throws DbException {
|
||||
@@ -438,27 +424,53 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
@Nullable
|
||||
@Override
|
||||
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();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, c, capacity, maxLatency);
|
||||
if (ids.isEmpty()) return null;
|
||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
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);
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
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
|
||||
@Override
|
||||
public Offer generateOffer(Transaction transaction, ContactId c,
|
||||
@@ -471,7 +483,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
|
||||
if (ids.isEmpty()) return null;
|
||||
for (MessageId m : ids)
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
return new Offer(ids);
|
||||
}
|
||||
|
||||
@@ -493,22 +505,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
@Nullable
|
||||
@Override
|
||||
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();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency);
|
||||
if (ids.isEmpty()) return null;
|
||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
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);
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
@@ -555,15 +567,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getGroup(txn, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getGroupId(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
return db.getGroupId(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata getGroupMetadata(Transaction transaction, GroupId g)
|
||||
throws DbException {
|
||||
@@ -632,24 +635,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageIds(txn, g, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
||||
ContactId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@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
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||
throws DbException {
|
||||
@@ -755,46 +740,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Message getMessageToSend(Transaction transaction, ContactId c,
|
||||
MessageId m, long maxLatency, boolean markAsSent)
|
||||
throws DbException {
|
||||
if (markAsSent && 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 {
|
||||
public Map<MessageId, Integer> getUnackedMessagesToSend(
|
||||
Transaction transaction,
|
||||
ContactId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
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
|
||||
public long getUnackedMessageBytesToSend(Transaction transaction,
|
||||
ContactId c) throws DbException {
|
||||
@@ -830,10 +785,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c,
|
||||
long maxLatency) throws DbException {
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c, maxLatency);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1041,8 +996,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
||||
affected));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1106,16 +1060,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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();
|
||||
db.lowerAckFlag(txn, c, acked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
||||
long duration) throws DbException {
|
||||
@@ -1162,8 +1106,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
List<ContactId> affected = singletonList(c);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
|
||||
List<ContactId> affected = Collections.singletonList(c);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1196,9 +1140,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (db.getMessageState(txn, m) != DELIVERED)
|
||||
throw new IllegalArgumentException("Shared undelivered message");
|
||||
db.setMessageShared(txn, m, true);
|
||||
GroupId g = db.getGroupId(txn, m);
|
||||
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
|
||||
transaction.attach(new MessageSharedEvent(m, g, visibility));
|
||||
transaction.attach(new MessageSharedEvent(m));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1212,28 +1154,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public void addMessageDependencies(Transaction transaction,
|
||||
Message dependent, Collection<MessageId> dependencies)
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
|
||||
interface DatabaseConstants {
|
||||
|
||||
/**
|
||||
@@ -23,6 +25,19 @@ interface DatabaseConstants {
|
||||
*/
|
||||
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
|
||||
* whether the database is marked as dirty.
|
||||
|
||||
@@ -85,17 +85,12 @@ class H2Database extends JdbcDatabase {
|
||||
public void close() throws DbException {
|
||||
// H2 will close the database when the last connection closes
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
c = createConnection();
|
||||
closeAllConnections();
|
||||
super.closeAllConnections();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.execute("SHUTDOWN COMPACT");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
tryToClose(c, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
closeAllConnections();
|
||||
super.closeAllConnections();
|
||||
c = createConnection();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
s.executeQuery("SHUTDOWN");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -106,7 +106,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
closeAllConnections();
|
||||
super.closeAllConnections();
|
||||
c = createConnection();
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
|
||||
@@ -51,6 +51,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
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.VARCHAR;
|
||||
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.WARNING;
|
||||
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.TIMER_NOT_STARTED;
|
||||
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.SHARED;
|
||||
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.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
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.ExponentialBackoff.calculateExpiry;
|
||||
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> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 50;
|
||||
|
||||
/**
|
||||
* The maximum number of idle connections to keep open.
|
||||
*/
|
||||
private static final int MAX_CONNECTION_POOL_SIZE = 1;
|
||||
static final int CODE_SCHEMA_VERSION = 49;
|
||||
|
||||
// Time period offsets for incoming transport keys
|
||||
private static final int OFFSET_PREV = -1;
|
||||
@@ -256,7 +252,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " maxLatency BIGINT," // Null if latency was reset
|
||||
+ " eta BIGINT NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, contactId),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
@@ -369,7 +365,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||
|
||||
@GuardedBy("connectionsLock")
|
||||
private final LinkedList<Connection> connectionPool = new LinkedList<>();
|
||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||
|
||||
@GuardedBy("connectionsLock")
|
||||
private int openConnections = 0;
|
||||
@@ -382,7 +378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
throws DbException, SQLException;
|
||||
|
||||
// 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;
|
||||
|
||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||
@@ -408,8 +405,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (reopen) {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
wasDirtyOnInitialisation = isDirty(s);
|
||||
boolean migrated = migrateSchema(txn, s, listener);
|
||||
compact = wasDirtyOnInitialisation || migrated;
|
||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
||||
} else {
|
||||
wasDirtyOnInitialisation = false;
|
||||
createTables(txn);
|
||||
@@ -433,11 +429,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
compactAndClose();
|
||||
logDuration(LOG, "Compacting database", start);
|
||||
// Allow the next transaction to reopen the DB
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
synchronized (connectionsLock) {
|
||||
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 Migration46_47(dbTypes),
|
||||
new Migration47_48(),
|
||||
new Migration48_49(),
|
||||
new Migration49_50()
|
||||
new Migration48_49()
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
@@ -510,6 +518,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
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) {
|
||||
return s.getBoolean(DIRTY_KEY, false);
|
||||
}
|
||||
@@ -523,6 +537,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private void initialiseSettings(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -577,8 +592,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
if (closed) throw new DbClosedException();
|
||||
txn = connectionPool.poll();
|
||||
logConnectionCounts();
|
||||
txn = connections.poll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
@@ -589,14 +603,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
txn.setAutoCommit(false);
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
// The DB may have been closed since the check above
|
||||
if (closed) {
|
||||
tryToClose(txn, LOG, WARNING);
|
||||
throw new DbClosedException();
|
||||
}
|
||||
openConnections++;
|
||||
logConnectionCounts();
|
||||
connectionsChanged.signalAll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
@@ -607,91 +614,67 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
return txn;
|
||||
}
|
||||
|
||||
@GuardedBy("connectionsLock")
|
||||
private void logConnectionCounts() {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine(openConnections + " connections open, "
|
||||
+ connectionPool.size() + " in pool");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
txn.rollback();
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
connections.add(txn);
|
||||
connectionsChanged.signalAll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
// Try to close the connection
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
closeConnection(txn);
|
||||
}
|
||||
|
||||
private void closeConnection(Connection txn) {
|
||||
tryToClose(txn, LOG, WARNING);
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
openConnections--;
|
||||
logConnectionCounts();
|
||||
connectionsChanged.signalAll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
tryToClose(txn, LOG, WARNING);
|
||||
// Whatever happens, allow the database to close
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
openConnections--;
|
||||
connectionsChanged.signalAll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitTransaction(Connection txn) throws DbException {
|
||||
// If the transaction commits successfully then return the connection
|
||||
// to the pool, otherwise close it
|
||||
try {
|
||||
txn.commit();
|
||||
returnConnectionToPool(txn);
|
||||
} catch (SQLException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
closeConnection(txn);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void returnConnectionToPool(Connection txn) {
|
||||
boolean shouldClose;
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
shouldClose = connectionPool.size() >= MAX_CONNECTION_POOL_SIZE;
|
||||
if (shouldClose) openConnections--;
|
||||
else connectionPool.add(txn);
|
||||
logConnectionCounts();
|
||||
connections.add(txn);
|
||||
connectionsChanged.signalAll();
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
if (shouldClose) tryToClose(txn, LOG, WARNING);
|
||||
}
|
||||
|
||||
void closeAllConnections() {
|
||||
void closeAllConnections() throws SQLException {
|
||||
boolean interrupted = false;
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
closed = true;
|
||||
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
||||
openConnections -= connectionPool.size();
|
||||
connectionPool.clear();
|
||||
for (Connection c : connections) c.close();
|
||||
openConnections -= connections.size();
|
||||
connections.clear();
|
||||
while (openConnections > 0) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Waiting for " + openConnections
|
||||
+ " connections to be closed");
|
||||
}
|
||||
try {
|
||||
connectionsChanged.await();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while closing connections");
|
||||
interrupted = true;
|
||||
}
|
||||
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
||||
openConnections -= connectionPool.size();
|
||||
connectionPool.clear();
|
||||
for (Connection c : connections) c.close();
|
||||
openConnections -= connections.size();
|
||||
connections.clear();
|
||||
}
|
||||
LOG.info("All connections closed");
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
@@ -934,10 +917,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount,"
|
||||
+ " maxLatency)"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount, eta)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
|
||||
+ " NULL)";
|
||||
+ " 0)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1147,8 +1129,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAcksToSend(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1160,7 +1142,34 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
boolean acksToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return acksToSend;
|
||||
if (acksToSend) return true;
|
||||
if (eager) {
|
||||
sql = "SELECT NULL from statuses"
|
||||
+ " 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());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
@@ -1280,46 +1289,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMessagesToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
if (eager) {
|
||||
String sql = "SELECT NULL from statuses"
|
||||
+ " 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());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||
throws DbException {
|
||||
@@ -1683,27 +1652,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getGroupId(Connection txn, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId FROM messages WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return g;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Group> getGroups(Connection txn, ClientId c,
|
||||
int majorVersion) throws DbException {
|
||||
@@ -1951,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
|
||||
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
|
||||
GroupId g) throws DbException {
|
||||
@@ -2268,6 +2191,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessagesToOffer(Connection txn,
|
||||
ContactId c, int maxMessages, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2276,14 +2200,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
ps.setLong(4, eta);
|
||||
ps.setInt(5, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
@@ -2324,9 +2247,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToSend(Connection txn,
|
||||
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
||||
int maxLength, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2335,21 +2259,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
ps.setLong(4, eta);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
if (capacity < RECORD_HEADER_BYTES + length) break;
|
||||
if (total + length > maxLength) break;
|
||||
ids.add(new MessageId(rs.getBytes(2)));
|
||||
capacity -= RECORD_HEADER_BYTES + length;
|
||||
total += length;
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -2362,12 +2286,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getUnackedMessagesToSend(Connection txn,
|
||||
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
|
||||
ContactId c) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM statuses"
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
@@ -2376,11 +2300,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
Map<MessageId, Integer> results = new LinkedHashMap<>();
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
MessageId id = new MessageId(rs.getBytes(2));
|
||||
results.put(id, length);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return ids;
|
||||
return results;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
@@ -2493,7 +2421,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
MessageId m = new MessageId(rs.getBytes(1));
|
||||
GroupId g = new GroupId(rs.getBytes(2));
|
||||
Collection<MessageId> messageIds = ids.get(g);
|
||||
//noinspection Java8MapApi
|
||||
if (messageIds == null) {
|
||||
messageIds = new ArrayList<>();
|
||||
ids.put(g, messageIds);
|
||||
@@ -2511,28 +2438,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Are any messages sendable immediately?
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
boolean found = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
if (found) return 0;
|
||||
// When is the earliest expiry time (could be in the past)?
|
||||
sql = "SELECT expiry FROM statuses"
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
@@ -2636,8 +2547,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
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 eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2646,21 +2558,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
ps.setLong(4, eta);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
if (capacity < RECORD_HEADER_BYTES + length) break;
|
||||
if (total + length > maxLength) break;
|
||||
ids.add(new MessageId(rs.getBytes(2)));
|
||||
capacity -= RECORD_HEADER_BYTES + length;
|
||||
total += length;
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -2814,7 +2726,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ContactId c = new ContactId(rs.getInt(1));
|
||||
TransportId t = new TransportId(rs.getString(2));
|
||||
Collection<TransportId> transportIds = ids.get(c);
|
||||
//noinspection Java8MapApi
|
||||
if (transportIds == null) {
|
||||
transportIds = new ArrayList<>();
|
||||
ids.put(c, transportIds);
|
||||
@@ -3379,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
|
||||
public void setCleanupTimerDuration(Connection txn, MessageId m,
|
||||
long duration) throws DbException {
|
||||
@@ -3730,8 +3617,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRetransmissionData(Connection txn, ContactId c,
|
||||
MessageId m, long maxLatency) throws DbException {
|
||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -3747,12 +3634,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs.close();
|
||||
ps.close();
|
||||
sql = "UPDATE statuses"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, eta = ?"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
||||
ps.setLong(2, maxLatency);
|
||||
ps.setLong(2, eta);
|
||||
ps.setBytes(3, m.getBytes());
|
||||
ps.setInt(4, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,18 @@
|
||||
package org.briarproject.bramble.io;
|
||||
|
||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@Module
|
||||
public class IoModule {
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.io;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
||||
import org.briarproject.bramble.api.system.Wakeful;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -29,12 +29,10 @@ 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.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.CREATED;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||
@@ -62,11 +60,12 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
private final List<Service> services;
|
||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||
private final List<ExecutorService> executors;
|
||||
private final Semaphore startStopSemaphore = new Semaphore(1);
|
||||
private final CountDownLatch dbLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
private final AtomicReference<LifecycleState> state =
|
||||
new AtomicReference<>(CREATED);
|
||||
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
@@ -103,8 +102,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
|
||||
@Override
|
||||
public StartResult startServices(SecretKey dbKey) {
|
||||
if (!state.compareAndSet(CREATED, STARTING)) {
|
||||
LOG.warning("Already running");
|
||||
if (!startStopSemaphore.tryAcquire()) {
|
||||
LOG.info("Already starting or stopping");
|
||||
return ALREADY_RUNNING;
|
||||
}
|
||||
long now = clock.currentTimeMillis();
|
||||
@@ -136,7 +135,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
});
|
||||
|
||||
LOG.info("Starting services");
|
||||
state.set(STARTING_SERVICES);
|
||||
state = STARTING_SERVICES;
|
||||
dbLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||
|
||||
@@ -149,7 +148,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
}
|
||||
|
||||
state.set(RUNNING);
|
||||
state = RUNNING;
|
||||
startupLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||
return SUCCESS;
|
||||
@@ -165,58 +164,59 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
} catch (ServiceException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return SERVICE_ERROR;
|
||||
} finally {
|
||||
startStopSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseMigration() {
|
||||
state.set(MIGRATING_DATABASE);
|
||||
state = MIGRATING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseCompaction() {
|
||||
state.set(COMPACTING_DATABASE);
|
||||
state = COMPACTING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
if (!state.compareAndSet(RUNNING, STOPPING)) {
|
||||
LOG.warning("Not running");
|
||||
try {
|
||||
startStopSemaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting to stop services");
|
||||
return;
|
||||
}
|
||||
LOG.info("Stopping services");
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
for (Service s : services) {
|
||||
try {
|
||||
try {
|
||||
LOG.info("Stopping services");
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
for (Service s : services) {
|
||||
long start = now();
|
||||
s.stopService();
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
logDuration(LOG, "Stopping service "
|
||||
+ s.getClass().getSimpleName(), start);
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
for (ExecutorService e : executors) {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Stopping executor "
|
||||
+ e.getClass().getSimpleName());
|
||||
for (ExecutorService e : executors) {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Stopping executor "
|
||||
+ e.getClass().getSimpleName());
|
||||
}
|
||||
e.shutdownNow();
|
||||
}
|
||||
e.shutdownNow();
|
||||
}
|
||||
try {
|
||||
long start = now();
|
||||
db.close();
|
||||
logDuration(LOG, "Closing database", start);
|
||||
} catch (DbException e) {
|
||||
shutdownLatch.countDown();
|
||||
} catch (DbException | ServiceException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
} finally {
|
||||
startStopSemaphore.release();
|
||||
}
|
||||
state.set(STOPPED);
|
||||
shutdownLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPED));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -236,6 +236,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state.get();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.briarproject.briar.android.logging;
|
||||
package org.briarproject.bramble.logging;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Formatter;
|
||||
@@ -18,16 +17,6 @@ import static java.util.Locale.US;
|
||||
@NotNullByDefault
|
||||
public class BriefLogFormatter extends Formatter {
|
||||
|
||||
public static String formatLog(Formatter formatter,
|
||||
Collection<LogRecord> logRecords) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (LogRecord record : logRecords) {
|
||||
String formatted = formatter.format(record);
|
||||
sb.append(formatted).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final DateFormat dateFormat; // Locking: lock
|
||||
private final Date date; // Locking: lock
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user