mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Compare commits
3 Commits
checkstyle
...
print-db-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6fa7520e9 | ||
|
|
d3bffaadf3 | ||
|
|
12b887881d |
@@ -118,3 +118,11 @@ mailbox integration test:
|
|||||||
- cd "$CI_PROJECT_DIR"
|
- cd "$CI_PROJECT_DIR"
|
||||||
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
||||||
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
||||||
|
|
||||||
|
pre_release_tests:
|
||||||
|
extends: .optional_tests
|
||||||
|
script:
|
||||||
|
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||||
|
timeout: 3h
|
||||||
|
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
|
|
||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10411
|
versionCode 10406
|
||||||
versionName "1.4.11"
|
versionName "1.4.6"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -18,7 +18,3 @@
|
|||||||
-dontnote com.google.common.**
|
-dontnote com.google.common.**
|
||||||
|
|
||||||
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
-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 * { *; }
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
import org.briarproject.bramble.battery.AndroidBatteryModule;
|
import org.briarproject.bramble.battery.AndroidBatteryModule;
|
||||||
import org.briarproject.bramble.io.DnsModule;
|
|
||||||
import org.briarproject.bramble.network.AndroidNetworkModule;
|
import org.briarproject.bramble.network.AndroidNetworkModule;
|
||||||
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
||||||
import org.briarproject.bramble.reporting.ReportingModule;
|
import org.briarproject.bramble.reporting.ReportingModule;
|
||||||
@@ -19,7 +18,6 @@ import dagger.Module;
|
|||||||
AndroidTaskSchedulerModule.class,
|
AndroidTaskSchedulerModule.class,
|
||||||
AndroidWakefulIoExecutorModule.class,
|
AndroidWakefulIoExecutorModule.class,
|
||||||
CircumventionModule.class,
|
CircumventionModule.class,
|
||||||
DnsModule.class,
|
|
||||||
ReportingModule.class,
|
ReportingModule.class,
|
||||||
SocksModule.class
|
SocksModule.class
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import android.net.LinkAddress;
|
|||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiInfo;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
@@ -24,6 +21,7 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -40,7 +38,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.content.Context.WIFI_SERVICE;
|
|
||||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
@@ -114,37 +111,15 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NetworkStatus getNetworkStatus() {
|
public NetworkStatus getNetworkStatus() {
|
||||||
// https://issuetracker.google.com/issues/175055271
|
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||||
try {
|
boolean connected = net != null && net.isConnected();
|
||||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
boolean wifi = false, ipv6Only = false;
|
||||||
boolean connected = net != null && net.isConnected();
|
if (connected) {
|
||||||
boolean wifi = false, ipv6Only = false;
|
wifi = net.getType() == TYPE_WIFI;
|
||||||
if (connected) {
|
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
||||||
wifi = net.getType() == TYPE_WIFI;
|
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
||||||
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
|
||||||
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
|
||||||
}
|
|
||||||
return new NetworkStatus(connected, wifi, ipv6Only);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
// Without the ConnectivityManager we can't detect whether we have
|
|
||||||
// internet access. Assume we do, which is probably less harmful
|
|
||||||
// than assuming we don't. Likewise, assume the connection is
|
|
||||||
// IPv6-only. Fall back to the WifiManager to detect whether we
|
|
||||||
// have a wifi connection.
|
|
||||||
LOG.info("ConnectivityManager is broken, guessing connectivity");
|
|
||||||
boolean connected = true, wifi = false, ipv6Only = true;
|
|
||||||
WifiManager wm = (WifiManager) app.getSystemService(WIFI_SERVICE);
|
|
||||||
if (wm != null) {
|
|
||||||
WifiInfo info = wm.getConnectionInfo();
|
|
||||||
if (info != null && info.getIpAddress() != 0) {
|
|
||||||
LOG.info("Connected to wifi");
|
|
||||||
wifi = true;
|
|
||||||
ipv6Only = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new NetworkStatus(connected, wifi, ipv6Only);
|
|
||||||
}
|
}
|
||||||
|
return new NetworkStatus(connected, wifi, ipv6Only);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,29 +130,23 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
*/
|
*/
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private boolean isActiveNetworkIpv6Only() {
|
private boolean isActiveNetworkIpv6Only() {
|
||||||
// https://issuetracker.google.com/issues/175055271
|
Network net = connectivityManager.getActiveNetwork();
|
||||||
try {
|
if (net == null) {
|
||||||
Network net = connectivityManager.getActiveNetwork();
|
LOG.info("No active network");
|
||||||
if (net == null) {
|
|
||||||
LOG.info("No active network");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LinkProperties props = connectivityManager.getLinkProperties(net);
|
|
||||||
if (props == null) {
|
|
||||||
LOG.info("No link properties for active network");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean hasIpv6Unicast = false;
|
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
|
||||||
InetAddress addr = linkAddress.getAddress();
|
|
||||||
if (addr instanceof Inet4Address) return false;
|
|
||||||
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
|
|
||||||
}
|
|
||||||
return hasIpv6Unicast;
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
LinkProperties props = connectivityManager.getLinkProperties(net);
|
||||||
|
if (props == null) {
|
||||||
|
LOG.info("No link properties for active network");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean hasIpv6Unicast = false;
|
||||||
|
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
||||||
|
InetAddress addr = linkAddress.getAddress();
|
||||||
|
if (addr instanceof Inet4Address) return false;
|
||||||
|
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
|
||||||
|
}
|
||||||
|
return hasIpv6Unicast;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,22 +32,13 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
|||||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
try {
|
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
try {
|
return app.getContentResolver().openOutputStream(Uri.parse(uri), "wt");
|
||||||
return app.getContentResolver()
|
|
||||||
.openOutputStream(Uri.parse(uri), "wt");
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,24 +175,16 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
@Nullable
|
@Nullable
|
||||||
private InetAddress getWifiClientIpv6Address() {
|
private InetAddress getWifiClientIpv6Address() {
|
||||||
// https://issuetracker.google.com/issues/175055271
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
try {
|
NetworkCapabilities caps =
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
connectivityManager.getNetworkCapabilities(net);
|
||||||
NetworkCapabilities caps =
|
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
LinkProperties props = connectivityManager.getLinkProperties(net);
|
||||||
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) {
|
if (props == null) continue;
|
||||||
continue;
|
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
||||||
}
|
InetAddress addr = linkAddress.getAddress();
|
||||||
LinkProperties props =
|
if (isIpv6LinkLocalAddress(addr)) return addr;
|
||||||
connectivityManager.getLinkProperties(net);
|
|
||||||
if (props == null) continue;
|
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
|
||||||
InetAddress addr = linkAddress.getAddress();
|
|
||||||
if (isIpv6LinkLocalAddress(addr)) return addr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (SecurityException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -235,17 +227,12 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
// network's socket factory may try to connect via another network
|
// network's socket factory may try to connect via another network
|
||||||
private SocketFactory getSocketFactory() {
|
private SocketFactory getSocketFactory() {
|
||||||
if (SDK_INT < 21) return SocketFactory.getDefault();
|
if (SDK_INT < 21) return SocketFactory.getDefault();
|
||||||
// https://issuetracker.google.com/issues/175055271
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
try {
|
NetworkCapabilities caps =
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
connectivityManager.getNetworkCapabilities(net);
|
||||||
NetworkCapabilities caps =
|
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
return net.getSocketFactory();
|
||||||
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
|
||||||
return net.getSocketFactory();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (SecurityException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
}
|
||||||
LOG.warning("Could not find suitable socket factory");
|
LOG.warning("Could not find suitable socket factory");
|
||||||
return SocketFactory.getDefault();
|
return SocketFactory.getDefault();
|
||||||
|
|||||||
@@ -11,35 +11,62 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AndroidTorPluginFactory extends TorPluginFactory {
|
public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(AndroidTorPluginFactory.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
|
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
|
||||||
|
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||||
|
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
||||||
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
|
private final Executor ioExecutor, wakefulIoExecutor;
|
||||||
private final Application app;
|
private final Application app;
|
||||||
|
private final NetworkManager networkManager;
|
||||||
|
private final LocationUtils locationUtils;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final SocketFactory torSocketFactory;
|
||||||
|
private final BackoffFactory backoffFactory;
|
||||||
|
private final ResourceProvider resourceProvider;
|
||||||
|
private final CircumventionProvider circumventionProvider;
|
||||||
|
private final BatteryManager batteryManager;
|
||||||
private final AndroidWakeLockManager wakeLockManager;
|
private final AndroidWakeLockManager wakeLockManager;
|
||||||
|
private final Clock clock;
|
||||||
|
private final File torDirectory;
|
||||||
|
private int torSocksPort;
|
||||||
|
private int torControlPort;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
|
Application app,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
@@ -48,43 +75,80 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
|||||||
ResourceProvider resourceProvider,
|
ResourceProvider resourceProvider,
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
|
AndroidWakeLockManager wakeLockManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
CryptoComponent crypto,
|
|
||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort,
|
@TorControlPort int torControlPort,
|
||||||
Application app,
|
CryptoComponent crypto) {
|
||||||
AndroidWakeLockManager wakeLockManager) {
|
this.ioExecutor = ioExecutor;
|
||||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
|
||||||
circumventionProvider, batteryManager, clock, crypto,
|
|
||||||
torDirectory, torSocksPort, torControlPort);
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this.networkManager = networkManager;
|
||||||
|
this.locationUtils = locationUtils;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.torSocketFactory = torSocketFactory;
|
||||||
|
this.backoffFactory = backoffFactory;
|
||||||
|
this.resourceProvider = resourceProvider;
|
||||||
|
this.circumventionProvider = circumventionProvider;
|
||||||
|
this.batteryManager = batteryManager;
|
||||||
this.wakeLockManager = wakeLockManager;
|
this.wakeLockManager = wakeLockManager;
|
||||||
|
this.clock = clock;
|
||||||
|
this.torDirectory = torDirectory;
|
||||||
|
this.torSocksPort = torSocksPort;
|
||||||
|
this.torControlPort = torControlPort;
|
||||||
|
this.crypto = crypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
String getArchitectureForTorBinary() {
|
public TransportId getId() {
|
||||||
for (String abi : getSupportedArchitectures()) {
|
return TorConstants.ID;
|
||||||
if (abi.startsWith("x86_64")) return "x86_64_pie";
|
}
|
||||||
else if (abi.startsWith("x86")) return "x86_pie";
|
|
||||||
else if (abi.startsWith("arm64")) return "arm64_pie";
|
@Override
|
||||||
else if (abi.startsWith("armeabi")) return "arm_pie";
|
public long getMaxLatency() {
|
||||||
|
return MAX_LATENCY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||||
|
|
||||||
|
// Check that we have a Tor binary for this architecture
|
||||||
|
String architecture = null;
|
||||||
|
for (String abi : AndroidUtils.getSupportedArchitectures()) {
|
||||||
|
if (abi.startsWith("x86_64")) {
|
||||||
|
architecture = "x86_64";
|
||||||
|
break;
|
||||||
|
} else if (abi.startsWith("x86")) {
|
||||||
|
architecture = "x86";
|
||||||
|
break;
|
||||||
|
} else if (abi.startsWith("arm64")) {
|
||||||
|
architecture = "arm64";
|
||||||
|
break;
|
||||||
|
} else if (abi.startsWith("armeabi")) {
|
||||||
|
architecture = "arm";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
if (architecture == null) {
|
||||||
}
|
LOG.info("Tor is not supported on this architecture");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Use position-independent executable
|
||||||
|
architecture += "_pie";
|
||||||
|
|
||||||
@Override
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
TorPlugin createPluginInstance(Backoff backoff,
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
TorRendezvousCrypto torRendezvousCrypto =
|
||||||
String architecture) {
|
new TorRendezvousCryptoImpl(crypto);
|
||||||
return new AndroidTorPlugin(ioExecutor,
|
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
|
||||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocketFactory, clock, resourceProvider,
|
||||||
circumventionProvider, batteryManager, wakeLockManager,
|
circumventionProvider, batteryManager, wakeLockManager,
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
backoff, torRendezvousCrypto, callback, architecture,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||||
torControlPort);
|
torControlPort);
|
||||||
|
eventBus.addListener(plugin);
|
||||||
|
return plugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.content.Intent;
|
|||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.AlarmListener;
|
import org.briarproject.bramble.api.system.AlarmListener;
|
||||||
@@ -117,12 +116,10 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
||||||
Runnable wakeful = () ->
|
Runnable wakeful = () ->
|
||||||
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
||||||
// Acquire the lock before scheduling the check to ensure the check
|
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
||||||
// doesn't access the task queue before the task has been added
|
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
|
||||||
ScheduledTask s;
|
cancelled);
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
|
||||||
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
|
|
||||||
tasks.add(s);
|
tasks.add(s);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
@@ -139,7 +136,6 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
return schedule(wrapped, executor, delay, unit, cancelled);
|
return schedule(wrapped, executor, delay, unit, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
||||||
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
||||||
this::runDueTasks, "TaskScheduler");
|
this::runDueTasks, "TaskScheduler");
|
||||||
@@ -210,7 +206,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
private final Future<?> check;
|
private final Future<?> check;
|
||||||
private final AtomicBoolean cancelled;
|
private final AtomicBoolean cancelled;
|
||||||
|
|
||||||
private ScheduledTask(Runnable task, long dueMillis,
|
public ScheduledTask(Runnable task, long dueMillis,
|
||||||
Future<?> check, AtomicBoolean cancelled) {
|
Future<?> check, AtomicBoolean cancelled) {
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.dueMillis = dueMillis;
|
this.dueMillis = dueMillis;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Looper;
|
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
@@ -135,8 +134,4 @@ public class AndroidUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUiThread() {
|
|
||||||
return Looper.myLooper() == Looper.getMainLooper();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ targetCompatibility = 1.8
|
|||||||
apply plugin: 'ru.vyarus.animalsniffer'
|
apply plugin: 'ru.vyarus.animalsniffer'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply from: 'witness.gradle'
|
apply from: 'witness.gradle'
|
||||||
apply plugin: 'checkstyle'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.google.dagger:dagger:$dagger_version"
|
implementation "com.google.dagger:dagger:$dagger_version"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package org.briarproject.bramble.api;
|
|
||||||
|
|
||||||
public interface Cancellable {
|
|
||||||
|
|
||||||
void cancel();
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,7 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -20,9 +19,10 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ClientHelper {
|
public interface ClientHelper {
|
||||||
|
|
||||||
@@ -127,17 +127,16 @@ public interface ClientHelper {
|
|||||||
BdfDictionary properties) throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and validate the elements of a Mailbox update message.
|
* Parse and validate the property dictionary of a Mailbox property update
|
||||||
|
* message.
|
||||||
*
|
*
|
||||||
* @return the parsed update message
|
* @return the properties for using the Mailbox, or null if there is no
|
||||||
* @throws FormatException if the message elements are invalid
|
* Mailbox available
|
||||||
|
* @throws FormatException if the properties are not valid
|
||||||
*/
|
*/
|
||||||
MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
|
@Nullable
|
||||||
BdfList serverSupports, BdfDictionary properties)
|
MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||||
throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
|
|
||||||
throws FormatException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the contact ID from the group metadata of the given contact
|
* Retrieves the contact ID from the group metadata of the given contact
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ConnectionManager {
|
public interface ConnectionManager {
|
||||||
@@ -17,17 +16,6 @@ public interface ConnectionManager {
|
|||||||
*/
|
*/
|
||||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
|
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.
|
* Manages an incoming connection from a contact over a duplex transport.
|
||||||
*/
|
*/
|
||||||
@@ -46,14 +34,6 @@ public interface ConnectionManager {
|
|||||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||||
TransportConnectionWriter w);
|
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.
|
* Manages an outgoing connection to a contact over a duplex transport.
|
||||||
*/
|
*/
|
||||||
@@ -66,21 +46,4 @@ public interface ConnectionManager {
|
|||||||
*/
|
*/
|
||||||
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
||||||
DuplexTransportConnection d);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,18 +33,11 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates the database implementation and exposes high-level operations
|
* Encapsulates the database implementation and exposes high-level operations
|
||||||
* to other components.
|
* to other components.
|
||||||
* <p>
|
|
||||||
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
|
|
||||||
* {@link #close()} methods, which must not be called concurrently, the
|
|
||||||
* database can be accessed from any thread. See {@link TransactionManager}
|
|
||||||
* for locking behaviour.
|
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface DatabaseComponent extends TransactionManager {
|
public interface DatabaseComponent extends TransactionManager {
|
||||||
|
|
||||||
@@ -126,11 +119,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
TransportKeys k) throws DbException;
|
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/>
|
* <p/>
|
||||||
* Read-only.
|
* 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
|
* 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;
|
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.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -207,15 +193,26 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a batch of messages for the given contact, for transmission over
|
* Returns a batch of messages for the given contact, with a total length
|
||||||
* a transport with the given maximum latency. The total length of the
|
* less than or equal to the given length, for transmission over a
|
||||||
* messages, including record headers, will be no more than the given
|
* transport with the given maximum latency. Returns null if there are no
|
||||||
* capacity. Returns null if there are no sendable messages that would fit
|
* sendable messages that fit in the given length.
|
||||||
* in the given capacity.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
long capacity, long maxLatency) throws DbException;
|
int maxLength, long maxLatency) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a batch of messages for the given contact containing the
|
||||||
|
* messages with the given IDs, for transmission over a transport with
|
||||||
|
* the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* If any of the given messages are not in the database or are not visible
|
||||||
|
* to the contact, they are omitted from the batch without throwing an
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
|
Collection<MessageId> ids, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an offer for the given contact for transmission over a
|
* Returns an offer for the given contact for transmission over a
|
||||||
@@ -235,16 +232,15 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a batch of messages for the given contact, for transmission over
|
* Returns a batch of messages for the given contact, with a total length
|
||||||
* a transport with the given maximum latency. Only messages that have been
|
* less than or equal to the given length, for transmission over a
|
||||||
* requested by the contact are returned. The total length of the messages,
|
* transport with the given maximum latency. Only messages that have been
|
||||||
* including record headers, will be no more than the given capacity.
|
* requested by the contact are returned. Returns null if there are no
|
||||||
* Returns null if there are no sendable messages that have been requested
|
* sendable messages that fit in the given length.
|
||||||
* by the contact and would fit in the given capacity.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
|
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
|
||||||
long capacity, long maxLatency) throws DbException;
|
int maxLength, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contact with the given ID.
|
* Returns the contact with the given ID.
|
||||||
@@ -283,13 +279,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
Group getGroup(Transaction txn, GroupId g) throws DbException;
|
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.
|
* Returns the metadata for the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -355,30 +344,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||||
Metadata query) throws DbException;
|
Metadata query) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IDs of 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.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -495,32 +460,15 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the message with the given ID for transmission to the given
|
|
||||||
* contact over a transport with the given maximum latency. Returns null
|
|
||||||
* if the message is no longer visible to the contact.
|
|
||||||
* <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
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
* given contact.
|
* given contact, together with their raw lengths. This may include
|
||||||
* <p>
|
* messages that have already been sent and are not yet due for
|
||||||
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, long)}
|
* retransmission.
|
||||||
* this method may return messages that have already been sent and are
|
|
||||||
* not yet due for retransmission.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getUnackedMessagesToSend(Transaction txn,
|
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
||||||
ContactId c) throws DbException;
|
ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -550,18 +498,15 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* 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
|
* message is due to be sent to the given contact. The returned value may
|
||||||
* the given latency.
|
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||||
* <p>
|
* no messages are scheduled to be sent.
|
||||||
* 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/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
|
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* Returns the pending contact with the given ID.
|
||||||
@@ -703,13 +648,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Records an ack for the given messages as having been sent to the given
|
|
||||||
* contact.
|
|
||||||
*/
|
|
||||||
void setAckSent(Transaction txn, ContactId c, Collection<MessageId> acked)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cleanup timer duration for the given message. This does not
|
* Sets the cleanup timer duration for the given message. This does not
|
||||||
* start the message's cleanup timer.
|
* start the message's cleanup timer.
|
||||||
@@ -756,13 +694,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void setMessageState(Transaction txn, MessageId m, MessageState state)
|
void setMessageState(Transaction txn, MessageId m, MessageState state)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the given messages as having been sent to the given contact
|
|
||||||
* over a transport with the given maximum latency.
|
|
||||||
*/
|
|
||||||
void setMessagesSent(Transaction txn, ContactId c,
|
|
||||||
Collection<MessageId> sent, long maxLatency) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds dependencies for a message
|
* Adds dependencies for a message
|
||||||
*/
|
*/
|
||||||
@@ -815,4 +746,6 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
|
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
void printStats(Transaction txn) throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
* submitted, tasks are not run concurrently, and submitting a task will never
|
* submitted, tasks are not run concurrently, and submitting a task will never
|
||||||
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
|
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
|
||||||
* discarded.
|
* discarded.
|
||||||
* <p>
|
|
||||||
* It is not mandatory to use this executor for database tasks. The database
|
|
||||||
* can be accessed from any thread, but this executor's guarantee that tasks
|
|
||||||
* are run in the order they're submitted may be useful in some cases.
|
|
||||||
*/
|
*/
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Target({FIELD, METHOD, PARAMETER})
|
@Target({FIELD, METHOD, PARAMETER})
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ public class Transaction {
|
|||||||
/**
|
/**
|
||||||
* Attaches an event to be broadcast when the transaction has been
|
* Attaches an event to be broadcast when the transaction has been
|
||||||
* committed. The event will be broadcast on the {@link EventExecutor}.
|
* committed. The event will be broadcast on the {@link EventExecutor}.
|
||||||
* Events and {@link #attach(Runnable) tasks} are submitted to the
|
|
||||||
* {@link EventExecutor} in the order they were attached to the
|
|
||||||
* transaction.
|
|
||||||
*/
|
*/
|
||||||
public void attach(Event e) {
|
public void attach(Event e) {
|
||||||
if (actions == null) actions = new ArrayList<>();
|
if (actions == null) actions = new ArrayList<>();
|
||||||
@@ -57,9 +54,6 @@ public class Transaction {
|
|||||||
/**
|
/**
|
||||||
* Attaches a task to be executed when the transaction has been
|
* Attaches a task to be executed when the transaction has been
|
||||||
* committed. The task will be run on the {@link EventExecutor}.
|
* committed. The task will be run on the {@link EventExecutor}.
|
||||||
* {@link #attach(Event) Events} and tasks are submitted to the
|
|
||||||
* {@link EventExecutor} in the order they were attached to the
|
|
||||||
* transaction.
|
|
||||||
*/
|
*/
|
||||||
public void attach(Runnable r) {
|
public void attach(Runnable r) {
|
||||||
if (actions == null) actions = new ArrayList<>();
|
if (actions == null) actions = new ArrayList<>();
|
||||||
|
|||||||
@@ -1,95 +1,51 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for managing database transactions.
|
|
||||||
* <p>
|
|
||||||
* Read-only transactions may access the database concurrently. Read-write
|
|
||||||
* transactions access the database exclusively, so starting a read-only or
|
|
||||||
* read-write transaction will block until there are no read-write
|
|
||||||
* transactions in progress.
|
|
||||||
* <p>
|
|
||||||
* Failing to {@link #endTransaction(Transaction) end} a transaction will
|
|
||||||
* prevent other callers from accessing the database, so it is recommended to
|
|
||||||
* use the {@link #transaction(boolean, DbRunnable)},
|
|
||||||
* {@link #transactionWithResult(boolean, DbCallable)} and
|
|
||||||
* {@link #transactionWithNullableResult(boolean, NullableDbCallable)} methods
|
|
||||||
* where possible, which handle committing or aborting the transaction on the
|
|
||||||
* caller's behalf.
|
|
||||||
* <p>
|
|
||||||
* Transactions are not reentrant, i.e. it is not permitted to start a
|
|
||||||
* transaction on a thread that already has a transaction in progress.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface TransactionManager {
|
public interface TransactionManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new transaction and returns an object representing it. This
|
* Starts a new transaction and returns an object representing it.
|
||||||
* method acquires the database lock, which is held until
|
* <p/>
|
||||||
* {@link #endTransaction(Transaction)} is called.
|
* This method acquires locks, so it must not be called while holding a
|
||||||
|
* lock.
|
||||||
*
|
*
|
||||||
* @param readOnly True if the transaction will only be used for reading,
|
* @param readOnly true if the transaction will only be used for reading.
|
||||||
* in which case the database lock can be shared with other read-only
|
|
||||||
* transactions.
|
|
||||||
*/
|
*/
|
||||||
Transaction startTransaction(boolean readOnly) throws DbException;
|
Transaction startTransaction(boolean readOnly) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits a transaction to the database.
|
* Commits a transaction to the database.
|
||||||
* {@link #endTransaction(Transaction)} must be called to release the
|
|
||||||
* database lock.
|
|
||||||
*/
|
*/
|
||||||
void commitTransaction(Transaction txn) throws DbException;
|
void commitTransaction(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends a transaction. If the transaction has not been committed by
|
* Ends a transaction. If the transaction has not been committed,
|
||||||
* calling {@link #commitTransaction(Transaction)}, it is aborted and the
|
* it will be aborted. If the transaction has been committed,
|
||||||
* database lock is released.
|
* any events attached to the transaction are broadcast.
|
||||||
* <p>
|
* The database lock will be released in either case.
|
||||||
* If the transaction has been committed, any
|
|
||||||
* {@link Transaction#attach events} attached to the transaction are
|
|
||||||
* broadcast and any {@link Transaction#attach(Runnable) tasks} attached
|
|
||||||
* to the transaction are submitted to the {@link EventExecutor}. The
|
|
||||||
* database lock is then released.
|
|
||||||
*/
|
*/
|
||||||
void endTransaction(Transaction txn);
|
void endTransaction(Transaction txn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction. The database lock is held
|
* Runs the given task within a transaction.
|
||||||
* while running the task.
|
|
||||||
*
|
|
||||||
* @param readOnly True if the transaction will only be used for reading,
|
|
||||||
* in which case the database lock can be shared with other read-only
|
|
||||||
* transactions.
|
|
||||||
*/
|
*/
|
||||||
<E extends Exception> void transaction(boolean readOnly,
|
<E extends Exception> void transaction(boolean readOnly,
|
||||||
DbRunnable<E> task) throws DbException, E;
|
DbRunnable<E> task) throws DbException, E;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction and returns the result of the
|
* Runs the given task within a transaction and returns the result of the
|
||||||
* task. The database lock is held while running the task.
|
* task.
|
||||||
*
|
|
||||||
* @param readOnly True if the transaction will only be used for reading,
|
|
||||||
* in which case the database lock can be shared with other read-only
|
|
||||||
* transactions.
|
|
||||||
*/
|
*/
|
||||||
<R, E extends Exception> R transactionWithResult(boolean readOnly,
|
<R, E extends Exception> R transactionWithResult(boolean readOnly,
|
||||||
DbCallable<R, E> task) throws DbException, E;
|
DbCallable<R, E> task) throws DbException, E;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction and returns the result of the
|
* Runs the given task within a transaction and returns the result of the
|
||||||
* task, which may be null. The database lock is held while running the
|
* task, which may be null.
|
||||||
* task.
|
|
||||||
*
|
|
||||||
* @param readOnly True if the transaction will only be used for reading,
|
|
||||||
* in which case the database lock can be shared with other read-only
|
|
||||||
* transactions.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,
|
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,
|
||||||
|
|||||||
@@ -37,14 +37,8 @@ public interface LifecycleManager {
|
|||||||
*/
|
*/
|
||||||
enum LifecycleState {
|
enum LifecycleState {
|
||||||
|
|
||||||
CREATED,
|
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
|
||||||
STARTING,
|
RUNNING, STOPPING;
|
||||||
MIGRATING_DATABASE,
|
|
||||||
COMPACTING_DATABASE,
|
|
||||||
STARTING_SERVICES,
|
|
||||||
RUNNING,
|
|
||||||
STOPPING,
|
|
||||||
STOPPED;
|
|
||||||
|
|
||||||
public boolean isAfter(LifecycleState state) {
|
public boolean isAfter(LifecycleState state) {
|
||||||
return ordinal() > state.ordinal();
|
return ordinal() > state.ordinal();
|
||||||
|
|||||||
@@ -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,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ public abstract class MailboxId extends UniqueId {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return fromHexString(token);
|
return fromHexString(token);
|
||||||
} catch (FormatException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new InvalidMailboxIdException();
|
throw new InvalidMailboxIdException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -43,14 +41,4 @@ public interface MailboxManager {
|
|||||||
*/
|
*/
|
||||||
boolean checkConnection();
|
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,58 +1,35 @@
|
|||||||
package org.briarproject.bramble.api.mailbox;
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxProperties {
|
public class MailboxProperties {
|
||||||
|
|
||||||
private final String onion;
|
private final String baseUrl;
|
||||||
private final MailboxAuthToken authToken;
|
private final MailboxAuthToken authToken;
|
||||||
private final boolean owner;
|
private final boolean owner;
|
||||||
private final List<MailboxVersion> serverSupports;
|
private final List<MailboxVersion> serverSupports;
|
||||||
@Nullable
|
|
||||||
private final MailboxFolderId inboxId; // Null for own mailbox
|
|
||||||
@Nullable
|
|
||||||
private final MailboxFolderId outboxId; // Null for own mailbox
|
|
||||||
|
|
||||||
/**
|
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||||
* Constructor for properties used by the mailbox's owner.
|
boolean owner, List<MailboxVersion> serverSupports) {
|
||||||
*/
|
this.baseUrl = baseUrl;
|
||||||
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
|
||||||
List<MailboxVersion> serverSupports) {
|
|
||||||
this.onion = onion;
|
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
this.owner = true;
|
this.owner = owner;
|
||||||
this.serverSupports = serverSupports;
|
this.serverSupports = serverSupports;
|
||||||
this.inboxId = null;
|
|
||||||
this.outboxId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getBaseUrl() {
|
||||||
* Constructor for properties used by a contact of the mailbox's owner.
|
return baseUrl;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the onion address of the mailbox, excluding the .onion suffix.
|
|
||||||
*/
|
|
||||||
public String getOnion() {
|
public String getOnion() {
|
||||||
return onion;
|
return baseUrl.replaceFirst("^http://", "")
|
||||||
|
.replaceFirst("\\.onion$", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public MailboxAuthToken getAuthToken() {
|
public MailboxAuthToken getAuthToken() {
|
||||||
@@ -66,33 +43,4 @@ public class MailboxProperties {
|
|||||||
public List<MailboxVersion> getServerSupports() {
|
public List<MailboxVersion> getServerSupports() {
|
||||||
return serverSupports;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxPropertiesUpdate {
|
||||||
|
|
||||||
|
private final String onion;
|
||||||
|
private final MailboxAuthToken authToken;
|
||||||
|
private final MailboxFolderId inboxId;
|
||||||
|
private final MailboxFolderId outboxId;
|
||||||
|
|
||||||
|
public MailboxPropertiesUpdate(String onion,
|
||||||
|
MailboxAuthToken authToken, MailboxFolderId inboxId,
|
||||||
|
MailboxFolderId outboxId) {
|
||||||
|
this.onion = onion;
|
||||||
|
this.authToken = authToken;
|
||||||
|
this.inboxId = inboxId;
|
||||||
|
this.outboxId = outboxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOnion() {
|
||||||
|
return onion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxAuthToken getAuthToken() {
|
||||||
|
return authToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxFolderId getInboxId() {
|
||||||
|
return inboxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxFolderId getOutboxId() {
|
||||||
|
return outboxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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 MailboxPropertyManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID of the mailbox property client.
|
||||||
|
*/
|
||||||
|
ClientId CLIENT_ID =
|
||||||
|
new ClientId("org.briarproject.bramble.mailbox.properties");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current major version of the mailbox property client.
|
||||||
|
*/
|
||||||
|
int MAJOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current minor version of the mailbox property client.
|
||||||
|
*/
|
||||||
|
int MINOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of properties required for a (non-empty) update message.
|
||||||
|
*/
|
||||||
|
int PROP_COUNT = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The required properties of a non-empty update message.
|
||||||
|
*/
|
||||||
|
String PROP_KEY_ONION = "onion";
|
||||||
|
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||||
|
String PROP_KEY_INBOXID = "inboxId";
|
||||||
|
String PROP_KEY_OUTBOXID = "outboxId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the Onion property.
|
||||||
|
*/
|
||||||
|
int PROP_ONION_LENGTH = 56;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message metadata key for the version number of a local or remote update,
|
||||||
|
* as a BDF long.
|
||||||
|
*/
|
||||||
|
String MSG_KEY_VERSION = "version";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message metadata key for whether an update is local or remote, as a BDF
|
||||||
|
* boolean.
|
||||||
|
*/
|
||||||
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -28,12 +26,10 @@ public interface MailboxSettingsManager {
|
|||||||
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
void removeOwnMailboxProperties(Transaction txn) throws DbException;
|
|
||||||
|
|
||||||
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||||
|
|
||||||
void recordSuccessfulConnection(Transaction txn, long now,
|
void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
List<MailboxVersion> versions) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
@@ -46,28 +42,19 @@ public interface MailboxSettingsManager {
|
|||||||
|
|
||||||
interface MailboxHook {
|
interface MailboxHook {
|
||||||
/**
|
/**
|
||||||
* Called when Briar is paired with a mailbox.
|
* Called when Briar is paired with a mailbox
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
|
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
||||||
*/
|
*/
|
||||||
void mailboxPaired(Transaction txn, MailboxProperties p)
|
void mailboxPaired(Transaction txn, String ownOnion)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mailbox is unpaired.
|
* Called when the mailbox is unpaired
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
*/
|
*/
|
||||||
void mailboxUnpaired(Transaction txn) throws DbException;
|
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 org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
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
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxStatus {
|
public class MailboxStatus {
|
||||||
|
|
||||||
private final long lastAttempt, lastSuccess;
|
private final long lastAttempt, lastSuccess;
|
||||||
private final int attemptsSinceSuccess;
|
private final int attemptsSinceSuccess;
|
||||||
private final List<MailboxVersion> serverSupports;
|
|
||||||
|
|
||||||
public MailboxStatus(long lastAttempt, long lastSuccess,
|
public MailboxStatus(long lastAttempt, long lastSuccess,
|
||||||
int attemptsSinceSuccess,
|
int attemptsSinceSuccess) {
|
||||||
List<MailboxVersion> serverSupports) {
|
|
||||||
this.lastAttempt = lastAttempt;
|
this.lastAttempt = lastAttempt;
|
||||||
this.lastSuccess = lastSuccess;
|
this.lastSuccess = lastSuccess;
|
||||||
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
||||||
this.serverSupports = serverSupports;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,28 +56,4 @@ public class MailboxStatus {
|
|||||||
public int getAttemptsSinceSuccess() {
|
public int getAttemptsSinceSuccess() {
|
||||||
return attemptsSinceSuccess;
|
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,8 +1,6 @@
|
|||||||
package org.briarproject.bramble.api.mailbox.event;
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.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 org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when {@link MailboxPropertiesUpdate} are received
|
||||||
|
* from a contact.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class RemoteMailboxPropertiesUpdateEvent extends Event {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
@Nullable
|
||||||
|
private final MailboxPropertiesUpdate mailboxPropertiesUpdate;
|
||||||
|
|
||||||
|
public RemoteMailboxPropertiesUpdateEvent(ContactId contactId,
|
||||||
|
@Nullable MailboxPropertiesUpdate mailboxPropertiesUpdate) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.mailboxPropertiesUpdate = mailboxPropertiesUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContact() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MailboxPropertiesUpdate getMailboxPropertiesUpdate() {
|
||||||
|
return mailboxPropertiesUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
public interface TorConstants {
|
public interface TorConstants {
|
||||||
|
|
||||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||||
@@ -12,9 +10,8 @@ public interface TorConstants {
|
|||||||
int DEFAULT_SOCKS_PORT = 59050;
|
int DEFAULT_SOCKS_PORT = 59050;
|
||||||
int DEFAULT_CONTROL_PORT = 59051;
|
int DEFAULT_CONTROL_PORT = 59051;
|
||||||
|
|
||||||
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
|
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
||||||
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
|
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
||||||
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
|
|
||||||
|
|
||||||
// Local settings (not shared with contacts)
|
// Local settings (not shared with contacts)
|
||||||
String PREF_TOR_NETWORK = "network2";
|
String PREF_TOR_NETWORK = "network2";
|
||||||
|
|||||||
@@ -12,6 +12,4 @@ public interface RecordWriter {
|
|||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
|
|
||||||
void close() throws IOException;
|
void close() throws IOException;
|
||||||
|
|
||||||
long getBytesWritten();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 writePriority(Priority p) throws IOException;
|
||||||
|
|
||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
|
|
||||||
long getBytesWritten();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,30 +12,12 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface SyncSessionFactory {
|
public interface SyncSessionFactory {
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a session for receiving data from a contact.
|
|
||||||
*/
|
|
||||||
SyncSession createIncomingSession(ContactId c, InputStream in,
|
SyncSession createIncomingSession(ContactId c, InputStream in,
|
||||||
PriorityHandler handler);
|
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,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, boolean eager, StreamWriter streamWriter);
|
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,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
@Nullable Priority priority);
|
@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.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class GroupVisibilityUpdatedEvent extends Event {
|
public class GroupVisibilityUpdatedEvent extends Event {
|
||||||
|
|
||||||
private final Visibility visibility;
|
|
||||||
private final Collection<ContactId> affected;
|
private final Collection<ContactId> affected;
|
||||||
|
|
||||||
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
||||||
Collection<ContactId> affected) {
|
|
||||||
this.visibility = visibility;
|
|
||||||
this.affected = affected;
|
this.affected = affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visibility getVisibility() {
|
|
||||||
return visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contacts affected by the update.
|
* Returns the contacts affected by the update.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
package org.briarproject.bramble.api.sync.event;
|
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.event.Event;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
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 org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,32 +14,12 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
public class MessageSharedEvent extends Event {
|
public class MessageSharedEvent extends Event {
|
||||||
|
|
||||||
private final MessageId messageId;
|
private final MessageId messageId;
|
||||||
private final GroupId groupId;
|
|
||||||
private final Map<ContactId, Boolean> groupVisibility;
|
|
||||||
|
|
||||||
public MessageSharedEvent(MessageId message, GroupId groupId,
|
public MessageSharedEvent(MessageId message) {
|
||||||
Map<ContactId, Boolean> groupVisibility) {
|
|
||||||
this.messageId = message;
|
this.messageId = message;
|
||||||
this.groupId = groupId;
|
|
||||||
this.groupVisibility = groupVisibility;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getMessageId() {
|
public MessageId getMessageId() {
|
||||||
return messageId;
|
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;
|
package org.briarproject.bramble.api.system;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -17,8 +16,6 @@ public interface TaskScheduler {
|
|||||||
* <p>
|
* <p>
|
||||||
* If the platform supports wake locks, a wake lock will be held while
|
* If the platform supports wake locks, a wake lock will be held while
|
||||||
* submitting and running the task.
|
* submitting and running the task.
|
||||||
*
|
|
||||||
* @return A {@link Cancellable} for cancelling the task.
|
|
||||||
*/
|
*/
|
||||||
Cancellable schedule(Runnable task, Executor executor, long delay,
|
Cancellable schedule(Runnable task, Executor executor, long delay,
|
||||||
TimeUnit unit);
|
TimeUnit unit);
|
||||||
@@ -30,11 +27,17 @@ public interface TaskScheduler {
|
|||||||
* <p>
|
* <p>
|
||||||
* If the platform supports wake locks, a wake lock will be held while
|
* If the platform supports wake locks, a wake lock will be held while
|
||||||
* submitting and running the task.
|
* submitting and running the task.
|
||||||
*
|
|
||||||
* @return A {@link Cancellable} for cancelling all future executions of
|
|
||||||
* the task.
|
|
||||||
*/
|
*/
|
||||||
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
||||||
long delay, long interval, TimeUnit unit);
|
long delay, long interval, TimeUnit unit);
|
||||||
|
|
||||||
|
interface Cancellable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the task if it has not already started running. If the task
|
||||||
|
* is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long, TimeUnit) periodic},
|
||||||
|
* all future executions of the task are cancelled.
|
||||||
|
*/
|
||||||
|
void cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@@ -96,10 +95,10 @@ public class StringUtils {
|
|||||||
/**
|
/**
|
||||||
* Converts the given hex string to a byte array.
|
* 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();
|
int len = hex.length();
|
||||||
if (len % 2 != 0)
|
if (len % 2 != 0)
|
||||||
throw new FormatException();
|
throw new IllegalArgumentException("Not a hex string");
|
||||||
byte[] bytes = new byte[len / 2];
|
byte[] bytes = new byte[len / 2];
|
||||||
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
||||||
int high = hexDigitToInt(hex.charAt(i));
|
int high = hexDigitToInt(hex.charAt(i));
|
||||||
@@ -109,11 +108,11 @@ public class StringUtils {
|
|||||||
return bytes;
|
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 >= '0' && c <= '9') return c - '0';
|
||||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
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) {
|
public static String trim(String s) {
|
||||||
@@ -131,13 +130,13 @@ public class StringUtils {
|
|||||||
return MAC.matcher(mac).matches();
|
return MAC.matcher(mac).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] macToBytes(String mac) throws FormatException {
|
public static byte[] macToBytes(String mac) {
|
||||||
if (!MAC.matcher(mac).matches()) throw new FormatException();
|
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||||
return fromHexString(mac.replaceAll(":", ""));
|
return fromHexString(mac.replaceAll(":", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String macToString(byte[] mac) throws FormatException {
|
public static String macToString(byte[] mac) {
|
||||||
if (mac.length != 6) throw new FormatException();
|
if (mac.length != 6) throw new IllegalArgumentException();
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
for (byte b : mac) {
|
for (byte b : mac) {
|
||||||
if (s.length() > 0) s.append(':');
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,12 +20,8 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
@@ -40,7 +36,6 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -51,7 +46,6 @@ import java.util.Random;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
@@ -228,19 +222,6 @@ public class TestUtils {
|
|||||||
getAgreementPublicKey(), verified);
|
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)
|
public static void writeBytes(File file, byte[] bytes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
FileOutputStream outputStream = new FileOutputStream(file);
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
@@ -293,30 +274,26 @@ public class TestUtils {
|
|||||||
return Math.sqrt(getVariance(samples));
|
return Math.sqrt(getVariance(samples));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOptionalTestEnabled(Class<?> testClass) {
|
public static boolean isOptionalTestEnabled(Class testClass) {
|
||||||
String optionalTests = System.getenv("OPTIONAL_TESTS");
|
String optionalTests = System.getenv("OPTIONAL_TESTS");
|
||||||
return optionalTests != null &&
|
return optionalTests != null &&
|
||||||
asList(optionalTests.split(",")).contains(testClass.getName());
|
asList(optionalTests.split(",")).contains(testClass.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean mailboxUpdateEqual(@Nullable MailboxUpdate a,
|
public static boolean mailboxPropertiesUpdateEqual(
|
||||||
@Nullable MailboxUpdate b) {
|
@Nullable MailboxPropertiesUpdate a,
|
||||||
|
@Nullable MailboxPropertiesUpdate b) {
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
if (!a.hasMailbox() && !b.hasMailbox()) {
|
return a.getOnion().equals(b.getOnion()) &&
|
||||||
return a.getClientSupports().equals(b.getClientSupports());
|
a.getAuthToken().equals(b.getAuthToken()) &&
|
||||||
} else if (a.hasMailbox() && b.hasMailbox()) {
|
a.getInboxId().equals(b.getInboxId()) &&
|
||||||
MailboxUpdateWithMailbox am = (MailboxUpdateWithMailbox) a;
|
a.getOutboxId().equals(b.getOutboxId());
|
||||||
MailboxUpdateWithMailbox bm = (MailboxUpdateWithMailbox) b;
|
|
||||||
return am.getClientSupports().equals(bm.getClientSupports()) &&
|
|
||||||
mailboxPropertiesEqual(am.getMailboxProperties(),
|
|
||||||
bm.getMailboxProperties());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean mailboxPropertiesEqual(@Nullable MailboxProperties a,
|
public static boolean mailboxPropertiesEqual(
|
||||||
|
@Nullable MailboxProperties a,
|
||||||
@Nullable MailboxProperties b) {
|
@Nullable MailboxProperties b) {
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return a == b;
|
return a == b;
|
||||||
@@ -337,24 +314,4 @@ public class TestUtils {
|
|||||||
}
|
}
|
||||||
return false;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ apply plugin: 'idea'
|
|||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply from: 'witness.gradle'
|
apply from: 'witness.gradle'
|
||||||
apply from: '../dagger.gradle'
|
apply from: '../dagger.gradle'
|
||||||
apply plugin: 'checkstyle'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-api', configuration: 'default')
|
implementation project(path: ':bramble-api', configuration: 'default')
|
||||||
@@ -17,7 +16,7 @@ dependencies {
|
|||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||||
implementation 'org.briarproject:jtorctl:0.5'
|
implementation 'org.briarproject:jtorctl:0.3'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.bramble.account;
|
package org.briarproject.bramble.account;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.DecryptionException;
|
import org.briarproject.bramble.api.crypto.DecryptionException;
|
||||||
@@ -210,13 +209,7 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
LOG.warning("Failed to load encrypted database key");
|
LOG.warning("Failed to load encrypted database key");
|
||||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||||
}
|
}
|
||||||
byte[] ciphertext;
|
byte[] ciphertext = fromHexString(hex);
|
||||||
try {
|
|
||||||
ciphertext = fromHexString(hex);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
LOG.warning("Encrypted database key has invalid format");
|
|
||||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
|
||||||
}
|
|
||||||
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
||||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||||
keyStrengthener);
|
keyStrengthener);
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -42,27 +39,25 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
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.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_ONION_LENGTH;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_ONION_LENGTH;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
@@ -417,28 +412,11 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
|
@Nullable
|
||||||
BdfList serverSupports, BdfDictionary properties)
|
public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||||
throws FormatException {
|
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()) {
|
if (properties.isEmpty()) {
|
||||||
// No mailbox -- cannot claim to support any API versions!
|
return null;
|
||||||
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
|
// Accepting more props than we need, for forward compatibility
|
||||||
if (properties.size() < PROP_COUNT) {
|
if (properties.size() < PROP_COUNT) {
|
||||||
@@ -457,27 +435,9 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
checkLength(inboxId, UniqueId.LENGTH);
|
checkLength(inboxId, UniqueId.LENGTH);
|
||||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||||
checkLength(outboxId, UniqueId.LENGTH);
|
checkLength(outboxId, UniqueId.LENGTH);
|
||||||
MailboxProperties props = new MailboxProperties(onion,
|
return new MailboxPropertiesUpdate(onion,
|
||||||
new MailboxAuthToken(authToken), serverSupportsList,
|
new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
|
||||||
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
|
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
|
@Override
|
||||||
|
|||||||
@@ -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];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
read(in, tag);
|
read(in, tag);
|
||||||
return 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.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
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.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
@@ -68,15 +67,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
TransportConnectionReader r) {
|
TransportConnectionReader r) {
|
||||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
syncSessionFactory, transportPropertyManager, t, r, null));
|
syncSessionFactory, transportPropertyManager, t, r));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void manageIncomingConnection(TransportId t,
|
|
||||||
TransportConnectionReader r, TagController c) {
|
|
||||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
|
||||||
syncSessionFactory, transportPropertyManager, t, r, c));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,16 +92,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
TransportConnectionWriter w) {
|
TransportConnectionWriter w) {
|
||||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
syncSessionFactory, transportPropertyManager, c, t, w, null));
|
syncSessionFactory, transportPropertyManager, c, t, w));
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package org.briarproject.bramble.connection;
|
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.connection.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
@@ -17,8 +15,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -27,8 +23,6 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
|
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportConnectionReader reader;
|
private final TransportConnectionReader reader;
|
||||||
@Nullable
|
|
||||||
private final TagController tagController;
|
|
||||||
|
|
||||||
IncomingSimplexSyncConnection(KeyManager keyManager,
|
IncomingSimplexSyncConnection(KeyManager keyManager,
|
||||||
ConnectionRegistry connectionRegistry,
|
ConnectionRegistry connectionRegistry,
|
||||||
@@ -36,50 +30,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriterFactory streamWriterFactory,
|
StreamWriterFactory streamWriterFactory,
|
||||||
SyncSessionFactory syncSessionFactory,
|
SyncSessionFactory syncSessionFactory,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
TransportId transportId,
|
TransportId transportId, TransportConnectionReader reader) {
|
||||||
TransportConnectionReader reader,
|
|
||||||
@Nullable TagController tagController) {
|
|
||||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||||
streamWriterFactory, syncSessionFactory,
|
streamWriterFactory, syncSessionFactory,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.reader = reader;
|
this.reader = reader;
|
||||||
this.tagController = tagController;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Read and recognise the tag
|
// Read and recognise the tag
|
||||||
byte[] tag;
|
StreamContext ctx = recogniseTag(reader, transportId);
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
LOG.info("Unrecognised tag");
|
LOG.info("Unrecognised tag");
|
||||||
onError();
|
onError(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ContactId contactId = ctx.getContactId();
|
ContactId contactId = ctx.getContactId();
|
||||||
if (contactId == null) {
|
if (contactId == null) {
|
||||||
LOG.warning("Received rendezvous stream, expected contact");
|
LOG.warning("Received rendezvous stream, expected contact");
|
||||||
onError(tag);
|
onError(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.isHandshakeMode()) {
|
if (ctx.isHandshakeMode()) {
|
||||||
// TODO: Support handshake mode for contacts
|
// TODO: Support handshake mode for contacts
|
||||||
LOG.warning("Received handshake tag, expected rotation mode");
|
LOG.warning("Received handshake tag, expected rotation mode");
|
||||||
onError(tag);
|
onError(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -88,33 +65,15 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
LOG.info("Ignoring priority for simplex connection");
|
LOG.info("Ignoring priority for simplex connection");
|
||||||
// Create and run the incoming session
|
// Create and run the incoming session
|
||||||
createIncomingSession(ctx, reader, handler).run();
|
createIncomingSession(ctx, reader, handler).run();
|
||||||
// Success
|
|
||||||
markTagAsRecognisedIfRequired(false, tag);
|
|
||||||
reader.dispose(false, true);
|
reader.dispose(false, true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
onError(tag);
|
onError(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError() {
|
private void onError(boolean recognised) {
|
||||||
disposeOnError(reader, false);
|
disposeOnError(reader, recognised);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
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.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
@@ -17,8 +16,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -29,8 +26,6 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportConnectionWriter writer;
|
private final TransportConnectionWriter writer;
|
||||||
@Nullable
|
|
||||||
private final OutgoingSessionRecord sessionRecord;
|
|
||||||
|
|
||||||
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
||||||
ConnectionRegistry connectionRegistry,
|
ConnectionRegistry connectionRegistry,
|
||||||
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
SyncSessionFactory syncSessionFactory,
|
SyncSessionFactory syncSessionFactory,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
ContactId contactId, TransportId transportId,
|
ContactId contactId, TransportId transportId,
|
||||||
TransportConnectionWriter writer,
|
TransportConnectionWriter writer) {
|
||||||
@Nullable OutgoingSessionRecord sessionRecord) {
|
|
||||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||||
streamWriterFactory, syncSessionFactory,
|
streamWriterFactory, syncSessionFactory,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
this.sessionRecord = sessionRecord;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
if (sessionRecord == null) {
|
// Use eager retransmission if the transport is lossy and cheap
|
||||||
// Use eager retransmission if the transport is lossy and cheap
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
||||||
ctx.getTransportId(), w.getMaxLatency(),
|
streamWriter);
|
||||||
w.isLossyAndCheap(), streamWriter);
|
|
||||||
} else {
|
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
|
||||||
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
|
|
||||||
sessionRecord);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,11 +163,16 @@ interface Database<T> {
|
|||||||
throws DbException;
|
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/>
|
* <p/>
|
||||||
* Read-only.
|
* 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
|
* 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;
|
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.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -320,13 +313,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Group getGroup(T txn, GroupId g) throws DbException;
|
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.
|
* Returns the metadata for the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -352,11 +338,8 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all contacts for which the given group's visibility
|
* Returns the IDs of all contacts to which the given group's visibility is
|
||||||
* is either {@link Visibility#SHARED shared} or
|
* either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
||||||
* {@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}.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
@@ -423,12 +406,6 @@ interface Database<T> {
|
|||||||
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
|
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of the given message in bytes, including the
|
|
||||||
* message header.
|
|
||||||
*/
|
|
||||||
int getMessageLength(T txn, MessageId m) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the metadata for all delivered messages in the given group.
|
* Returns the metadata for all delivered messages in the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -519,8 +496,7 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact. The total length of the messages including record headers
|
* given contact, up to the given total length.
|
||||||
* will be no more than the given capacity.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
||||||
* does not return messages that have already been sent unless they are
|
* does not return messages that have already been sent unless they are
|
||||||
@@ -528,20 +504,20 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity,
|
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
||||||
long maxLatency) throws DbException;
|
long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all messages that are eligible to be sent to the
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
* given contact.
|
* given contact, together with their raw lengths.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Unlike {@link #getMessagesToSend(Object, ContactId, long, long)} this
|
* Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
|
||||||
* method may return messages that have already been sent and are not yet
|
* method may return messages that have already been sent and are not yet
|
||||||
* due for retransmission.
|
* due for retransmission.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c)
|
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -597,16 +573,13 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* 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
|
* message is due to be sent to the given contact. The returned value may
|
||||||
* the given latency.
|
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||||
* <p>
|
* if no messages are scheduled to be sent.
|
||||||
* 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/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
long getNextSendTime(T txn, ContactId c, long maxLatency)
|
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* 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
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact and have been requested by the contact. The total length
|
* given contact and have been requested by the contact, up to the given
|
||||||
* of the messages including record headers will be no more than the given
|
* total length.
|
||||||
* capacity.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
|
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
|
||||||
long capacity, long maxLatency) throws DbException;
|
int maxLength, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all settings in the given namespace.
|
* Returns all settings in the given namespace.
|
||||||
@@ -890,4 +862,6 @@ interface Database<T> {
|
|||||||
* Stores the given transport keys, deleting any keys they have replaced.
|
* Stores the given transport keys, deleting any keys they have replaced.
|
||||||
*/
|
*/
|
||||||
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
|
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
|
||||||
|
|
||||||
|
void printStats(T txn) throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import org.briarproject.bramble.api.transport.TransportKeys;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -86,7 +87,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new MessageAddedEvent(m, null));
|
transaction.attach(new MessageAddedEvent(m, null));
|
||||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||||
DELIVERED));
|
DELIVERED));
|
||||||
if (shared) {
|
if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
|
||||||
Map<ContactId, Boolean> visibility =
|
|
||||||
db.getGroupVisibility(txn, m.getGroupId());
|
|
||||||
transaction.attach(new MessageSharedEvent(m.getId(),
|
|
||||||
m.getGroupId(), visibility));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||||
}
|
}
|
||||||
@@ -347,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
||||||
throws DbException {
|
long maxLatency, boolean eager) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
return db.containsAcksToSend(txn, c);
|
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -378,15 +373,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsIdentity(txn, a);
|
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
|
@Override
|
||||||
public boolean containsPendingContact(Transaction transaction,
|
public boolean containsPendingContact(Transaction transaction,
|
||||||
PendingContactId p) throws DbException {
|
PendingContactId p) throws DbException {
|
||||||
@@ -438,14 +424,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Collection<Message> generateBatch(Transaction transaction,
|
public Collection<Message> generateBatch(Transaction transaction,
|
||||||
ContactId c, long capacity, long maxLatency) throws DbException {
|
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, c, capacity, maxLatency);
|
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
if (ids.isEmpty()) return null;
|
|
||||||
long totalLength = 0;
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
@@ -454,11 +439,38 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Message> generateBatch(Transaction transaction,
|
||||||
|
ContactId c, Collection<MessageId> ids, long maxLatency)
|
||||||
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
long totalLength = 0;
|
||||||
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
|
List<MessageId> sentIds = new ArrayList<>(ids.size());
|
||||||
|
for (MessageId m : ids) {
|
||||||
|
if (db.containsVisibleMessage(txn, c, m)) {
|
||||||
|
Message message = db.getMessage(txn, m);
|
||||||
|
totalLength += message.getRawLength();
|
||||||
|
messages.add(message);
|
||||||
|
sentIds.add(m);
|
||||||
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messages.isEmpty()) return messages;
|
||||||
|
db.lowerRequestedFlag(txn, c, sentIds);
|
||||||
|
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Offer generateOffer(Transaction transaction, ContactId c,
|
public Offer generateOffer(Transaction transaction, ContactId c,
|
||||||
@@ -493,14 +505,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Collection<Message> generateRequestedBatch(Transaction transaction,
|
public Collection<Message> generateRequestedBatch(Transaction transaction,
|
||||||
ContactId c, long capacity, long maxLatency) throws DbException {
|
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency);
|
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
if (ids.isEmpty()) return null;
|
|
||||||
long totalLength = 0;
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
@@ -509,6 +520,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
@@ -555,15 +567,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getGroup(txn, g);
|
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
|
@Override
|
||||||
public Metadata getGroupMetadata(Transaction transaction, GroupId g)
|
public Metadata getGroupMetadata(Transaction transaction, GroupId g)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -632,24 +635,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageIds(txn, g, query);
|
return db.getMessageIds(txn, g, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
|
||||||
ContactId c) 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
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -755,31 +740,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public Message getMessageToSend(Transaction transaction, ContactId c,
|
public Map<MessageId, Integer> getUnackedMessagesToSend(
|
||||||
MessageId m, long maxLatency, boolean markAsSent)
|
Transaction transaction,
|
||||||
throws DbException {
|
ContactId c) throws DbException {
|
||||||
if (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 {
|
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
@@ -830,10 +794,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Transaction transaction, ContactId c,
|
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||||
long maxLatency) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getNextSendTime(txn, c, maxLatency);
|
return db.getNextSendTime(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1041,8 +1005,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.getGroupVisibility(txn, id).keySet();
|
db.getGroupVisibility(txn, id).keySet();
|
||||||
db.removeGroup(txn, id);
|
db.removeGroup(txn, id);
|
||||||
transaction.attach(new GroupRemovedEvent(g));
|
transaction.attach(new GroupRemovedEvent(g));
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
affected));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1106,16 +1069,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.removeTransportKeys(txn, t, k);
|
db.removeTransportKeys(txn, t, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAckSent(Transaction transaction, ContactId c,
|
|
||||||
Collection<MessageId> acked) throws DbException {
|
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
db.lowerAckFlag(txn, c, acked);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
||||||
long duration) throws DbException {
|
long duration) throws DbException {
|
||||||
@@ -1162,8 +1115,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
List<ContactId> affected = singletonList(c);
|
List<ContactId> affected = Collections.singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1196,9 +1149,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (db.getMessageState(txn, m) != DELIVERED)
|
if (db.getMessageState(txn, m) != DELIVERED)
|
||||||
throw new IllegalArgumentException("Shared undelivered message");
|
throw new IllegalArgumentException("Shared undelivered message");
|
||||||
db.setMessageShared(txn, m, true);
|
db.setMessageShared(txn, m, true);
|
||||||
GroupId g = db.getGroupId(txn, m);
|
transaction.attach(new MessageSharedEvent(m));
|
||||||
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
|
|
||||||
transaction.attach(new MessageSharedEvent(m, g, visibility));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1212,28 +1163,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new MessageStateChangedEvent(m, false, state));
|
transaction.attach(new MessageStateChangedEvent(m, false, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMessagesSent(Transaction transaction, ContactId c,
|
|
||||||
Collection<MessageId> sent, long maxLatency) throws DbException {
|
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
long totalLength = 0;
|
|
||||||
List<MessageId> visible = new ArrayList<>(sent.size());
|
|
||||||
for (MessageId m : sent) {
|
|
||||||
if (db.containsVisibleMessage(txn, c, m)) {
|
|
||||||
visible.add(m);
|
|
||||||
totalLength += db.getMessageLength(txn, m);
|
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.lowerRequestedFlag(txn, c, visible);
|
|
||||||
if (!visible.isEmpty()) {
|
|
||||||
transaction.attach(new MessagesSentEvent(c, visible, totalLength));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessageDependencies(Transaction transaction,
|
public void addMessageDependencies(Transaction transaction,
|
||||||
Message dependent, Collection<MessageId> dependencies)
|
Message dependent, Collection<MessageId> dependencies)
|
||||||
@@ -1327,6 +1256,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStats(Transaction transaction) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
db.printStats(txn);
|
||||||
|
}
|
||||||
|
|
||||||
private class CommitActionVisitor implements Visitor {
|
private class CommitActionVisitor implements Visitor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,8 +13,12 @@ import org.briarproject.bramble.util.StringUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
|
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,6 +106,73 @@ class H2Database extends JdbcDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStats(Connection txn) throws DbException {
|
||||||
|
List<String> names = printNames(txn);
|
||||||
|
for (String table : names) {
|
||||||
|
tryPrintStats(txn, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> printNames(Connection txn) throws DbException {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql =
|
||||||
|
"SELECT table_catalog, table_schema, table_name FROM INFORMATION_SCHEMA.TABLES";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
String catalog = rs.getString(1);
|
||||||
|
String schema = rs.getString(2);
|
||||||
|
String name = rs.getString(3);
|
||||||
|
LOG.info(
|
||||||
|
String.format("Table %s %s %s", catalog, schema, name));
|
||||||
|
names.add(schema + "." + name);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryPrintStats(Connection txn, String table) {
|
||||||
|
try {
|
||||||
|
printStats(txn, table);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printStats(Connection txn, String table) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "CALL DISK_SPACE_USED(?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setString(1, table);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) {
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
long size = rs.getLong(1);
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
LOG.info(String.format("Size of table %s: %d", table, size));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Connection createConnection() throws DbException, SQLException {
|
protected Connection createConnection() throws DbException, SQLException {
|
||||||
SecretKey key = this.key;
|
SecretKey key = this.key;
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStats(Connection txn) throws DbException {
|
||||||
|
// Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Connection createConnection() throws DbException, SQLException {
|
protected Connection createConnection() throws DbException, SQLException {
|
||||||
SecretKey key = this.key;
|
SecretKey key = this.key;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -69,14 +70,12 @@ import static java.sql.Types.BOOLEAN;
|
|||||||
import static java.sql.Types.INTEGER;
|
import static java.sql.Types.INTEGER;
|
||||||
import static java.sql.Types.VARCHAR;
|
import static java.sql.Types.VARCHAR;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.logging.Level.FINE;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -103,11 +102,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 50;
|
static final int CODE_SCHEMA_VERSION = 50;
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of idle connections to keep open.
|
|
||||||
*/
|
|
||||||
private static final int MAX_CONNECTION_POOL_SIZE = 1;
|
|
||||||
|
|
||||||
// Time period offsets for incoming transport keys
|
// Time period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
private static final int OFFSET_CURR = 0;
|
private static final int OFFSET_CURR = 0;
|
||||||
@@ -369,7 +363,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
@GuardedBy("connectionsLock")
|
@GuardedBy("connectionsLock")
|
||||||
private final LinkedList<Connection> connectionPool = new LinkedList<>();
|
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||||
|
|
||||||
@GuardedBy("connectionsLock")
|
@GuardedBy("connectionsLock")
|
||||||
private int openConnections = 0;
|
private int openConnections = 0;
|
||||||
@@ -577,8 +571,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) throw new DbClosedException();
|
if (closed) throw new DbClosedException();
|
||||||
txn = connectionPool.poll();
|
txn = connections.poll();
|
||||||
logConnectionCounts();
|
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -589,14 +582,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
txn.setAutoCommit(false);
|
txn.setAutoCommit(false);
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
// The DB may have been closed since the check above
|
|
||||||
if (closed) {
|
|
||||||
tryToClose(txn, LOG, WARNING);
|
|
||||||
throw new DbClosedException();
|
|
||||||
}
|
|
||||||
openConnections++;
|
openConnections++;
|
||||||
logConnectionCounts();
|
|
||||||
connectionsChanged.signalAll();
|
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -607,91 +593,67 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
return txn;
|
return txn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("connectionsLock")
|
|
||||||
private void logConnectionCounts() {
|
|
||||||
if (LOG.isLoggable(FINE)) {
|
|
||||||
LOG.fine(openConnections + " connections open, "
|
|
||||||
+ connectionPool.size() + " in pool");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abortTransaction(Connection txn) {
|
public void abortTransaction(Connection txn) {
|
||||||
// The transaction may have been aborted due to an earlier exception,
|
|
||||||
// so close the connection rather than returning it to the pool
|
|
||||||
try {
|
try {
|
||||||
txn.rollback();
|
txn.rollback();
|
||||||
|
connectionsLock.lock();
|
||||||
|
try {
|
||||||
|
connections.add(txn);
|
||||||
|
connectionsChanged.signalAll();
|
||||||
|
} finally {
|
||||||
|
connectionsLock.unlock();
|
||||||
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
// Try to close the connection
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
tryToClose(txn, LOG, WARNING);
|
||||||
closeConnection(txn);
|
// Whatever happens, allow the database to close
|
||||||
}
|
connectionsLock.lock();
|
||||||
|
try {
|
||||||
private void closeConnection(Connection txn) {
|
openConnections--;
|
||||||
tryToClose(txn, LOG, WARNING);
|
connectionsChanged.signalAll();
|
||||||
connectionsLock.lock();
|
} finally {
|
||||||
try {
|
connectionsLock.unlock();
|
||||||
openConnections--;
|
}
|
||||||
logConnectionCounts();
|
|
||||||
connectionsChanged.signalAll();
|
|
||||||
} finally {
|
|
||||||
connectionsLock.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commitTransaction(Connection txn) throws DbException {
|
public void commitTransaction(Connection txn) throws DbException {
|
||||||
// If the transaction commits successfully then return the connection
|
|
||||||
// to the pool, otherwise close it
|
|
||||||
try {
|
try {
|
||||||
txn.commit();
|
txn.commit();
|
||||||
returnConnectionToPool(txn);
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
closeConnection(txn);
|
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void returnConnectionToPool(Connection txn) {
|
|
||||||
boolean shouldClose;
|
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
shouldClose = connectionPool.size() >= MAX_CONNECTION_POOL_SIZE;
|
connections.add(txn);
|
||||||
if (shouldClose) openConnections--;
|
|
||||||
else connectionPool.add(txn);
|
|
||||||
logConnectionCounts();
|
|
||||||
connectionsChanged.signalAll();
|
connectionsChanged.signalAll();
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
if (shouldClose) tryToClose(txn, LOG, WARNING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeAllConnections() {
|
void closeAllConnections() throws SQLException {
|
||||||
boolean interrupted = false;
|
boolean interrupted = false;
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
closed = true;
|
closed = true;
|
||||||
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
for (Connection c : connections) c.close();
|
||||||
openConnections -= connectionPool.size();
|
openConnections -= connections.size();
|
||||||
connectionPool.clear();
|
connections.clear();
|
||||||
while (openConnections > 0) {
|
while (openConnections > 0) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Waiting for " + openConnections
|
|
||||||
+ " connections to be closed");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
connectionsChanged.await();
|
connectionsChanged.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.warning("Interrupted while closing connections");
|
LOG.warning("Interrupted while closing connections");
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
}
|
}
|
||||||
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
for (Connection c : connections) c.close();
|
||||||
openConnections -= connectionPool.size();
|
openConnections -= connections.size();
|
||||||
connectionPool.clear();
|
connections.clear();
|
||||||
}
|
}
|
||||||
LOG.info("All connections closed");
|
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -1147,8 +1109,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAcksToSend(Connection txn, ContactId c)
|
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
||||||
throws DbException {
|
long maxLatency, boolean eager) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -1160,7 +1122,34 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
boolean acksToSend = rs.next();
|
boolean acksToSend = rs.next();
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.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();
|
||||||
|
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) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps, LOG, WARNING);
|
tryToClose(ps, LOG, WARNING);
|
||||||
@@ -1280,46 +1269,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
|
@Override
|
||||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -1683,27 +1632,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
|
@Override
|
||||||
public Collection<Group> getGroups(Connection txn, ClientId c,
|
public Collection<Group> getGroups(Connection txn, ClientId c,
|
||||||
int majorVersion) throws DbException {
|
int majorVersion) throws DbException {
|
||||||
@@ -1951,31 +1879,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMessageLength(Connection txn, MessageId m)
|
|
||||||
throws DbException {
|
|
||||||
PreparedStatement ps = null;
|
|
||||||
ResultSet rs = null;
|
|
||||||
try {
|
|
||||||
String sql = "SELECT length from messages"
|
|
||||||
+ " WHERE messageId = ? AND state = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, m.getBytes());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
if (!rs.next()) throw new DbStateException();
|
|
||||||
int length = rs.getInt(1);
|
|
||||||
if (rs.next()) throw new DbStateException();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return length;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(rs, LOG, WARNING);
|
|
||||||
tryToClose(ps, LOG, WARNING);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
|
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
|
||||||
GroupId g) throws DbException {
|
GroupId g) throws DbException {
|
||||||
@@ -2324,8 +2227,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToSend(Connection txn,
|
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
||||||
ContactId c, long capacity, long maxLatency) throws DbException {
|
int maxLength, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2345,11 +2248,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setLong(4, maxLatency);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
|
int total = 0;
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
int length = rs.getInt(1);
|
int length = rs.getInt(1);
|
||||||
if (capacity < RECORD_HEADER_BYTES + length) break;
|
if (total + length > maxLength) break;
|
||||||
ids.add(new MessageId(rs.getBytes(2)));
|
ids.add(new MessageId(rs.getBytes(2)));
|
||||||
capacity -= RECORD_HEADER_BYTES + length;
|
total += length;
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2362,12 +2266,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getUnackedMessagesToSend(Connection txn,
|
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
|
||||||
ContactId c) throws DbException {
|
ContactId c) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT messageId FROM statuses"
|
String sql = "SELECT length, messageId FROM statuses"
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
@@ -2376,11 +2280,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
Map<MessageId, Integer> results = new LinkedHashMap<>();
|
||||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
while (rs.next()) {
|
||||||
|
int length = rs.getInt(1);
|
||||||
|
MessageId id = new MessageId(rs.getBytes(2));
|
||||||
|
results.put(id, length);
|
||||||
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
return ids;
|
return results;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps, LOG, WARNING);
|
tryToClose(ps, LOG, WARNING);
|
||||||
@@ -2493,7 +2401,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
MessageId m = new MessageId(rs.getBytes(1));
|
MessageId m = new MessageId(rs.getBytes(1));
|
||||||
GroupId g = new GroupId(rs.getBytes(2));
|
GroupId g = new GroupId(rs.getBytes(2));
|
||||||
Collection<MessageId> messageIds = ids.get(g);
|
Collection<MessageId> messageIds = ids.get(g);
|
||||||
//noinspection Java8MapApi
|
|
||||||
if (messageIds == null) {
|
if (messageIds == null) {
|
||||||
messageIds = new ArrayList<>();
|
messageIds = new ArrayList<>();
|
||||||
ids.put(g, messageIds);
|
ids.put(g, messageIds);
|
||||||
@@ -2511,28 +2418,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
public long getNextSendTime(Connection txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
// Are any messages sendable immediately?
|
String sql = "SELECT expiry FROM statuses"
|
||||||
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"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
@@ -2636,7 +2527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
||||||
ContactId c, long capacity, long maxLatency) throws DbException {
|
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2656,11 +2547,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setLong(4, maxLatency);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
|
int total = 0;
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
int length = rs.getInt(1);
|
int length = rs.getInt(1);
|
||||||
if (capacity < RECORD_HEADER_BYTES + length) break;
|
if (total + length > maxLength) break;
|
||||||
ids.add(new MessageId(rs.getBytes(2)));
|
ids.add(new MessageId(rs.getBytes(2)));
|
||||||
capacity -= RECORD_HEADER_BYTES + length;
|
total += length;
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2814,7 +2706,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ContactId c = new ContactId(rs.getInt(1));
|
ContactId c = new ContactId(rs.getInt(1));
|
||||||
TransportId t = new TransportId(rs.getString(2));
|
TransportId t = new TransportId(rs.getString(2));
|
||||||
Collection<TransportId> transportIds = ids.get(c);
|
Collection<TransportId> transportIds = ids.get(c);
|
||||||
//noinspection Java8MapApi
|
|
||||||
if (transportIds == null) {
|
if (transportIds == null) {
|
||||||
transportIds = new ArrayList<>();
|
transportIds = new ArrayList<>();
|
||||||
ids.put(c, transportIds);
|
ids.put(c, transportIds);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.briarproject.bramble.io;
|
package org.briarproject.bramble.io;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
||||||
import org.briarproject.bramble.api.system.Wakeful;
|
import org.briarproject.bramble.api.system.Wakeful;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
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.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
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.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.MIGRATING_DATABASE;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
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;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
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.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
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<Service> services;
|
||||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||||
private final List<ExecutorService> executors;
|
private final List<ExecutorService> executors;
|
||||||
|
private final Semaphore startStopSemaphore = new Semaphore(1);
|
||||||
private final CountDownLatch dbLatch = new CountDownLatch(1);
|
private final CountDownLatch dbLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||||
private final AtomicReference<LifecycleState> state =
|
|
||||||
new AtomicReference<>(CREATED);
|
private volatile LifecycleState state = STARTING;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
@@ -103,8 +102,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StartResult startServices(SecretKey dbKey) {
|
public StartResult startServices(SecretKey dbKey) {
|
||||||
if (!state.compareAndSet(CREATED, STARTING)) {
|
if (!startStopSemaphore.tryAcquire()) {
|
||||||
LOG.warning("Already running");
|
LOG.info("Already starting or stopping");
|
||||||
return ALREADY_RUNNING;
|
return ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
@@ -136,7 +135,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
LOG.info("Starting services");
|
LOG.info("Starting services");
|
||||||
state.set(STARTING_SERVICES);
|
state = STARTING_SERVICES;
|
||||||
dbLatch.countDown();
|
dbLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||||
|
|
||||||
@@ -149,7 +148,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set(RUNNING);
|
state = RUNNING;
|
||||||
startupLatch.countDown();
|
startupLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
@@ -165,58 +164,59 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
} catch (ServiceException e) {
|
} catch (ServiceException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return SERVICE_ERROR;
|
return SERVICE_ERROR;
|
||||||
|
} finally {
|
||||||
|
startStopSemaphore.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseMigration() {
|
public void onDatabaseMigration() {
|
||||||
state.set(MIGRATING_DATABASE);
|
state = MIGRATING_DATABASE;
|
||||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseCompaction() {
|
public void onDatabaseCompaction() {
|
||||||
state.set(COMPACTING_DATABASE);
|
state = COMPACTING_DATABASE;
|
||||||
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServices() {
|
public void stopServices() {
|
||||||
if (!state.compareAndSet(RUNNING, STOPPING)) {
|
try {
|
||||||
LOG.warning("Not running");
|
startStopSemaphore.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.warning("Interrupted while waiting to stop services");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG.info("Stopping services");
|
try {
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
LOG.info("Stopping services");
|
||||||
for (Service s : services) {
|
state = STOPPING;
|
||||||
try {
|
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||||
|
for (Service s : services) {
|
||||||
long start = now();
|
long start = now();
|
||||||
s.stopService();
|
s.stopService();
|
||||||
if (LOG.isLoggable(FINE)) {
|
if (LOG.isLoggable(FINE)) {
|
||||||
logDuration(LOG, "Stopping service "
|
logDuration(LOG, "Stopping service "
|
||||||
+ s.getClass().getSimpleName(), start);
|
+ s.getClass().getSimpleName(), start);
|
||||||
}
|
}
|
||||||
} catch (ServiceException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
}
|
||||||
}
|
for (ExecutorService e : executors) {
|
||||||
for (ExecutorService e : executors) {
|
if (LOG.isLoggable(FINE)) {
|
||||||
if (LOG.isLoggable(FINE)) {
|
LOG.fine("Stopping executor "
|
||||||
LOG.fine("Stopping executor "
|
+ e.getClass().getSimpleName());
|
||||||
+ e.getClass().getSimpleName());
|
}
|
||||||
|
e.shutdownNow();
|
||||||
}
|
}
|
||||||
e.shutdownNow();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
long start = now();
|
long start = now();
|
||||||
db.close();
|
db.close();
|
||||||
logDuration(LOG, "Closing database", start);
|
logDuration(LOG, "Closing database", start);
|
||||||
} catch (DbException e) {
|
shutdownLatch.countDown();
|
||||||
|
} catch (DbException | ServiceException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
|
} finally {
|
||||||
|
startStopSemaphore.release();
|
||||||
}
|
}
|
||||||
state.set(STOPPED);
|
|
||||||
shutdownLatch.countDown();
|
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -236,6 +236,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifecycleState getLifecycleState() {
|
public LifecycleState getLifecycleState() {
|
||||||
return state.get();
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for calling an API endpoint with the option to retry the call.
|
|
||||||
*/
|
|
||||||
interface ApiCall {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method makes a synchronous call to an API endpoint and returns
|
|
||||||
* true if the call should be retried, in which case the method may be
|
|
||||||
* called again on the same {@link ApiCall} instance after a delay.
|
|
||||||
*
|
|
||||||
* @return True if the API call needs to be retried, or false if the API
|
|
||||||
* call succeeded or {@link TolerableFailureException failed tolerably}.
|
|
||||||
*/
|
|
||||||
@IoExecutor
|
|
||||||
boolean callApi();
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for checking whether a mailbox is reachable.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
interface ConnectivityChecker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the checker. Any current connectivity check is cancelled.
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a connectivity check if needed and calls the given observer when
|
|
||||||
* the check succeeds. If a check is already running then the observer is
|
|
||||||
* called when the check succeeds. If a connectivity check has recently
|
|
||||||
* succeeded then the observer is called immediately.
|
|
||||||
* <p>
|
|
||||||
* Observers are removed after being called, or when the checker is
|
|
||||||
* {@link #destroy() destroyed}.
|
|
||||||
*/
|
|
||||||
void checkConnectivity(MailboxProperties properties,
|
|
||||||
ConnectivityObserver o);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an observer that was added via
|
|
||||||
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
|
|
||||||
* there are no remaining observers and a connectivity check is running
|
|
||||||
* then the check will be cancelled.
|
|
||||||
*/
|
|
||||||
void removeObserver(ConnectivityObserver o);
|
|
||||||
|
|
||||||
interface ConnectivityObserver {
|
|
||||||
void onConnectivityCheckSucceeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If no more than this much time has elapsed since the last connectivity
|
|
||||||
* check succeeded, consider the result to be fresh and don't check again.
|
|
||||||
* <p>
|
|
||||||
* Package access for testing.
|
|
||||||
*/
|
|
||||||
static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000;
|
|
||||||
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
protected final Clock clock;
|
|
||||||
private final MailboxApiCaller mailboxApiCaller;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private boolean destroyed = false;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private Cancellable connectivityCheck = null;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private long lastConnectivityCheckSucceeded = 0;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private final List<ConnectivityObserver> connectivityObservers =
|
|
||||||
new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an {@link ApiCall} for checking whether the mailbox is
|
|
||||||
* reachable. The {@link ApiCall} should call
|
|
||||||
* {@link #onConnectivityCheckSucceeded(long)} if the check succeeds.
|
|
||||||
*/
|
|
||||||
abstract ApiCall createConnectivityCheckTask(MailboxProperties properties);
|
|
||||||
|
|
||||||
ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) {
|
|
||||||
this.clock = clock;
|
|
||||||
this.mailboxApiCaller = mailboxApiCaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
synchronized (lock) {
|
|
||||||
destroyed = true;
|
|
||||||
connectivityObservers.clear();
|
|
||||||
if (connectivityCheck != null) {
|
|
||||||
connectivityCheck.cancel();
|
|
||||||
connectivityCheck = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkConnectivity(MailboxProperties properties,
|
|
||||||
ConnectivityObserver o) {
|
|
||||||
boolean callNow = false;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (destroyed) return;
|
|
||||||
if (connectivityCheck == null) {
|
|
||||||
// No connectivity check is running
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
if (now - lastConnectivityCheckSucceeded
|
|
||||||
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
|
||||||
// The last connectivity check is stale, start a new one
|
|
||||||
connectivityObservers.add(o);
|
|
||||||
ApiCall task = createConnectivityCheckTask(properties);
|
|
||||||
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
|
||||||
} else {
|
|
||||||
// The last connectivity check is fresh
|
|
||||||
callNow = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// A connectivity check is running, wait for it to succeed
|
|
||||||
connectivityObservers.add(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (callNow) o.onConnectivityCheckSucceeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onConnectivityCheckSucceeded(long now) {
|
|
||||||
List<ConnectivityObserver> observers;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (destroyed) return;
|
|
||||||
connectivityCheck = null;
|
|
||||||
lastConnectivityCheckSucceeded = now;
|
|
||||||
observers = new ArrayList<>(connectivityObservers);
|
|
||||||
connectivityObservers.clear();
|
|
||||||
}
|
|
||||||
for (ConnectivityObserver o : observers) {
|
|
||||||
o.onConnectivityCheckSucceeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeObserver(ConnectivityObserver o) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (destroyed) return;
|
|
||||||
connectivityObservers.remove(o);
|
|
||||||
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
|
|
||||||
connectivityCheck.cancel();
|
|
||||||
connectivityCheck = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class ContactMailboxClient implements MailboxClient {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(ContactMailboxClient.class.getName());
|
|
||||||
|
|
||||||
private final MailboxWorkerFactory workerFactory;
|
|
||||||
private final ConnectivityChecker connectivityChecker;
|
|
||||||
private final TorReachabilityMonitor reachabilityMonitor;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private MailboxWorker uploadWorker = null, downloadWorker = null;
|
|
||||||
|
|
||||||
ContactMailboxClient(MailboxWorkerFactory workerFactory,
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor) {
|
|
||||||
this.workerFactory = workerFactory;
|
|
||||||
this.connectivityChecker = connectivityChecker;
|
|
||||||
this.reachabilityMonitor = reachabilityMonitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
LOG.info("Started");
|
|
||||||
// Nothing to do until contact is assigned
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
LOG.info("Destroyed");
|
|
||||||
MailboxWorker uploadWorker, downloadWorker;
|
|
||||||
synchronized (lock) {
|
|
||||||
uploadWorker = this.uploadWorker;
|
|
||||||
this.uploadWorker = null;
|
|
||||||
downloadWorker = this.downloadWorker;
|
|
||||||
this.downloadWorker = null;
|
|
||||||
}
|
|
||||||
if (uploadWorker != null) uploadWorker.destroy();
|
|
||||||
if (downloadWorker != null) downloadWorker.destroy();
|
|
||||||
// The connectivity checker belongs to the client, so it should be
|
|
||||||
// destroyed. The Tor reachability monitor is shared between clients,
|
|
||||||
// so it should not be destroyed
|
|
||||||
connectivityChecker.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assignContactForUpload(ContactId contactId,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId) {
|
|
||||||
LOG.info("Contact assigned for upload");
|
|
||||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
// For a contact's mailbox we should always be uploading to the outbox
|
|
||||||
// assigned to us by the contact
|
|
||||||
if (!folderId.equals(properties.getOutboxId())) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
|
||||||
connectivityChecker, properties, folderId, contactId);
|
|
||||||
synchronized (lock) {
|
|
||||||
if (this.uploadWorker != null) throw new IllegalStateException();
|
|
||||||
this.uploadWorker = uploadWorker;
|
|
||||||
}
|
|
||||||
uploadWorker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deassignContactForUpload(ContactId contactId) {
|
|
||||||
LOG.info("Contact deassigned for upload");
|
|
||||||
MailboxWorker uploadWorker;
|
|
||||||
synchronized (lock) {
|
|
||||||
uploadWorker = this.uploadWorker;
|
|
||||||
this.uploadWorker = null;
|
|
||||||
}
|
|
||||||
if (uploadWorker != null) uploadWorker.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assignContactForDownload(ContactId contactId,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId) {
|
|
||||||
LOG.info("Contact assigned for download");
|
|
||||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
// For a contact's mailbox we should always be downloading from the
|
|
||||||
// inbox assigned to us by the contact
|
|
||||||
if (!folderId.equals(properties.getInboxId())) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
MailboxWorker downloadWorker =
|
|
||||||
workerFactory.createDownloadWorkerForContactMailbox(
|
|
||||||
connectivityChecker, reachabilityMonitor, properties);
|
|
||||||
synchronized (lock) {
|
|
||||||
if (this.downloadWorker != null) throw new IllegalStateException();
|
|
||||||
this.downloadWorker = downloadWorker;
|
|
||||||
}
|
|
||||||
downloadWorker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deassignContactForDownload(ContactId contactId) {
|
|
||||||
LOG.info("Contact deassigned for download");
|
|
||||||
MailboxWorker downloadWorker;
|
|
||||||
synchronized (lock) {
|
|
||||||
downloadWorker = this.downloadWorker;
|
|
||||||
this.downloadWorker = null;
|
|
||||||
}
|
|
||||||
if (downloadWorker != null) downloadWorker.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(ContactMailboxConnectivityChecker.class.getName());
|
|
||||||
|
|
||||||
private final MailboxApi mailboxApi;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ContactMailboxConnectivityChecker(Clock clock,
|
|
||||||
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
|
||||||
super(clock, mailboxApiCaller);
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
|
||||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
return new SimpleApiCall(() -> {
|
|
||||||
LOG.info("Checking connectivity of contact's mailbox");
|
|
||||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
|
||||||
LOG.info("Contact's mailbox is reachable");
|
|
||||||
// Call the observers and cache the result
|
|
||||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
|
||||||
|
|
||||||
ContactMailboxDownloadWorker(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor torReachabilityMonitor,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxFileManager mailboxFileManager,
|
|
||||||
MailboxProperties mailboxProperties) {
|
|
||||||
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
|
||||||
mailboxApi, mailboxFileManager, mailboxProperties);
|
|
||||||
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ApiCall createApiCallForDownloadCycle() {
|
|
||||||
return new SimpleApiCall(this::apiCallListInbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apiCallListInbox() throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
LOG.info("Listing inbox");
|
|
||||||
MailboxFolderId folderId =
|
|
||||||
requireNonNull(mailboxProperties.getInboxId());
|
|
||||||
List<MailboxFile> files;
|
|
||||||
try {
|
|
||||||
files = mailboxApi.getFiles(mailboxProperties, folderId);
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
LOG.warning("Inbox folder does not exist");
|
|
||||||
files = emptyList();
|
|
||||||
}
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
onDownloadCycleFinished();
|
|
||||||
} else {
|
|
||||||
Queue<FolderFile> queue = new LinkedList<>();
|
|
||||||
for (MailboxFile file : files) {
|
|
||||||
queue.add(new FolderFile(folderId, file.name));
|
|
||||||
}
|
|
||||||
downloadNextFile(queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -18,12 +16,8 @@ import java.util.List;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxApi {
|
interface MailboxApi {
|
||||||
|
|
||||||
List<MailboxVersion> getServerSupports(MailboxProperties properties)
|
|
||||||
throws IOException, ApiException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the mailbox with the setup token.
|
* Sets up the mailbox with the setup token.
|
||||||
*
|
*
|
||||||
@@ -91,13 +85,9 @@ interface MailboxApi {
|
|||||||
* Used by owner and contacts to list their files to retrieve.
|
* Used by owner and contacts to list their files to retrieve.
|
||||||
* <p>
|
* <p>
|
||||||
* Returns 200 OK with the list of files in JSON.
|
* Returns 200 OK with the list of files in JSON.
|
||||||
*
|
|
||||||
* @throws TolerableFailureException if response code is 404 (folder does
|
|
||||||
* not exist or client is not authorised to download from it)
|
|
||||||
*/
|
*/
|
||||||
List<MailboxFile> getFiles(MailboxProperties properties,
|
List<MailboxFile> getFiles(MailboxProperties properties,
|
||||||
MailboxFolderId folderId)
|
MailboxFolderId folderId) throws IOException, ApiException;
|
||||||
throws IOException, ApiException, TolerableFailureException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by owner and contacts to retrieve a file.
|
* Used by owner and contacts to retrieve a file.
|
||||||
@@ -106,22 +96,17 @@ interface MailboxApi {
|
|||||||
* in the response body.
|
* in the response body.
|
||||||
*
|
*
|
||||||
* @param file the empty file the response bytes will be written into.
|
* @param file the empty file the response bytes will be written into.
|
||||||
* @throws TolerableFailureException if response code is 404 (folder does
|
|
||||||
* not exist, client is not authorised to download from folder, or file
|
|
||||||
* does not exist)
|
|
||||||
*/
|
*/
|
||||||
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId, File file)
|
MailboxFileId fileId, File file) throws IOException, ApiException;
|
||||||
throws IOException, ApiException, TolerableFailureException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by owner and contacts to delete files.
|
* Used by owner and contacts to delete files.
|
||||||
* <p>
|
* <p>
|
||||||
* Returns 200 OK (no exception) if deletion was successful.
|
* Returns 200 OK (no exception) if deletion was successful.
|
||||||
*
|
*
|
||||||
* @throws TolerableFailureException if response code is 404 (folder does
|
* @throws TolerableFailureException on 404 response,
|
||||||
* not exist, client is not authorised to download from folder, or file
|
* because file was most likely deleted already.
|
||||||
* does not exist)
|
|
||||||
*/
|
*/
|
||||||
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId)
|
MailboxFileId fileId)
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxApiCaller {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The minimum interval between retries in milliseconds.
|
|
||||||
*/
|
|
||||||
long MIN_RETRY_INTERVAL_MS = MINUTES.toMillis(1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum interval between retries in milliseconds.
|
|
||||||
*/
|
|
||||||
long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously calls the given API call on the {@link IoExecutor},
|
|
||||||
* automatically retrying at increasing intervals until the API call
|
|
||||||
* returns false or retries are cancelled.
|
|
||||||
* <p>
|
|
||||||
* This method is safe to call while holding a lock.
|
|
||||||
*
|
|
||||||
* @return A {@link Cancellable} that can be used to cancel any future
|
|
||||||
* retries.
|
|
||||||
*/
|
|
||||||
Cancellable retryWithBackoff(ApiCall apiCall);
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxApiCallerImpl implements MailboxApiCaller {
|
|
||||||
|
|
||||||
private final TaskScheduler taskScheduler;
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxApiCallerImpl(TaskScheduler taskScheduler,
|
|
||||||
@IoExecutor Executor ioExecutor) {
|
|
||||||
this.taskScheduler = taskScheduler;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cancellable retryWithBackoff(ApiCall apiCall) {
|
|
||||||
Task task = new Task(apiCall);
|
|
||||||
task.start();
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Task implements Cancellable {
|
|
||||||
|
|
||||||
private final ApiCall apiCall;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private Cancellable scheduledTask = null;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private boolean cancelled = false;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private long retryIntervalMs = MIN_RETRY_INTERVAL_MS;
|
|
||||||
|
|
||||||
private Task(ApiCall apiCall) {
|
|
||||||
this.apiCall = apiCall;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (cancelled) throw new AssertionError();
|
|
||||||
ioExecutor.execute(this::callApi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void callApi() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (cancelled) return;
|
|
||||||
}
|
|
||||||
// The call returns true if we should retry
|
|
||||||
if (apiCall.callApi()) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (cancelled) return;
|
|
||||||
scheduledTask = taskScheduler.schedule(this::callApi,
|
|
||||||
ioExecutor, retryIntervalMs, MILLISECONDS);
|
|
||||||
// Increase the retry interval each time we retry
|
|
||||||
retryIntervalMs =
|
|
||||||
min(MAX_RETRY_INTERVAL_MS, retryIntervalMs * 2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
synchronized (lock) {
|
|
||||||
scheduledTask = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
Cancellable scheduledTask;
|
|
||||||
synchronized (lock) {
|
|
||||||
cancelled = true;
|
|
||||||
scheduledTask = this.scheduledTask;
|
|
||||||
this.scheduledTask = null;
|
|
||||||
}
|
|
||||||
if (scheduledTask != null) scheduledTask.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -34,7 +35,6 @@ import okhttp3.Response;
|
|||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
||||||
import static java.util.Collections.sort;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
@@ -42,39 +42,18 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxApiImpl implements MailboxApi {
|
class MailboxApiImpl implements MailboxApi {
|
||||||
|
|
||||||
|
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
||||||
|
private final JsonMapper mapper = JsonMapper.builder()
|
||||||
|
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
|
||||||
|
.build();
|
||||||
private static final MediaType JSON =
|
private static final MediaType JSON =
|
||||||
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
|
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
|
||||||
private static final MediaType FILE =
|
private static final MediaType FILE =
|
||||||
requireNonNull(MediaType.parse("application/octet-stream"));
|
requireNonNull(MediaType.parse("application/octet-stream"));
|
||||||
|
|
||||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
|
||||||
private final JsonMapper mapper = JsonMapper.builder()
|
|
||||||
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
|
|
||||||
.build();
|
|
||||||
private final UrlConverter urlConverter;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider,
|
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
|
||||||
UrlConverter urlConverter) {
|
|
||||||
this.httpClientProvider = httpClientProvider;
|
this.httpClientProvider = httpClientProvider;
|
||||||
this.urlConverter = urlConverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<MailboxVersion> getServerSupports(MailboxProperties properties)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
Response response = sendGetRequest(properties, "/versions");
|
|
||||||
if (response.code() != 200) throw new ApiException();
|
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
|
||||||
if (body == null) throw new ApiException();
|
|
||||||
try {
|
|
||||||
JsonNode node = mapper.readTree(body.string());
|
|
||||||
return parseServerSupports(node);
|
|
||||||
} catch (JacksonException e) {
|
|
||||||
throw new ApiException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,7 +61,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(getBaseUrl(properties) + "/setup")
|
.url(properties.getBaseUrl() + "/setup")
|
||||||
.put(EMPTY_REQUEST)
|
.put(EMPTY_REQUEST)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -97,42 +76,36 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (tokenNode == null) {
|
if (tokenNode == null) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
}
|
}
|
||||||
return new MailboxProperties(properties.getOnion(),
|
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||||
|
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
|
||||||
|
for (JsonNode versionNode : serverSupportsNode) {
|
||||||
|
if (!versionNode.isObject()) throw new ApiException();
|
||||||
|
ObjectNode objectNode = (ObjectNode) versionNode;
|
||||||
|
JsonNode majorNode = objectNode.get("major");
|
||||||
|
JsonNode minorNode = objectNode.get("minor");
|
||||||
|
if (majorNode == null || !majorNode.isNumber()) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
if (minorNode == null || !minorNode.isNumber()) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
int major = majorNode.asInt();
|
||||||
|
int minor = minorNode.asInt();
|
||||||
|
if (major < 0 || minor < 0) throw new ApiException();
|
||||||
|
serverSupports.add(new MailboxVersion(major, minor));
|
||||||
|
}
|
||||||
|
return new MailboxProperties(properties.getBaseUrl(),
|
||||||
MailboxAuthToken.fromString(tokenNode.textValue()),
|
MailboxAuthToken.fromString(tokenNode.textValue()),
|
||||||
parseServerSupports(node));
|
true, serverSupports);
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MailboxVersion> parseServerSupports(JsonNode node)
|
|
||||||
throws ApiException {
|
|
||||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
|
||||||
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
|
|
||||||
for (JsonNode versionNode : serverSupportsNode) {
|
|
||||||
if (!versionNode.isObject()) throw new ApiException();
|
|
||||||
ObjectNode objectNode = (ObjectNode) versionNode;
|
|
||||||
JsonNode majorNode = objectNode.get("major");
|
|
||||||
JsonNode minorNode = objectNode.get("minor");
|
|
||||||
if (majorNode == null || !majorNode.isNumber()) {
|
|
||||||
throw new ApiException();
|
|
||||||
}
|
|
||||||
if (minorNode == null || !minorNode.isNumber()) {
|
|
||||||
throw new ApiException();
|
|
||||||
}
|
|
||||||
int major = majorNode.asInt();
|
|
||||||
int minor = minorNode.asInt();
|
|
||||||
if (major < 0 || minor < 0) throw new ApiException();
|
|
||||||
serverSupports.add(new MailboxVersion(major, minor));
|
|
||||||
}
|
|
||||||
// Sort the list of versions for easier comparison
|
|
||||||
sort(serverSupports);
|
|
||||||
return serverSupports;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkStatus(MailboxProperties properties)
|
public boolean checkStatus(MailboxProperties properties)
|
||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Response response = sendGetRequest(properties, "/status");
|
Response response = sendGetRequest(properties, "/status");
|
||||||
if (response.code() == 401) throw new ApiException();
|
if (response.code() == 401) throw new ApiException();
|
||||||
return response.isSuccessful();
|
return response.isSuccessful();
|
||||||
@@ -143,7 +116,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(getBaseUrl(properties) + "/")
|
.url(properties.getBaseUrl() + "/")
|
||||||
.delete()
|
.delete()
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -168,7 +141,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
||||||
throws IOException, ApiException, TolerableFailureException {
|
throws IOException, ApiException, TolerableFailureException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
String url = getBaseUrl(properties) + "/contacts/" +
|
String url = properties.getBaseUrl() + "/contacts/" +
|
||||||
contactId.getInt();
|
contactId.getInt();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.delete()
|
.delete()
|
||||||
@@ -218,11 +191,9 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MailboxFile> getFiles(MailboxProperties properties,
|
public List<MailboxFile> getFiles(MailboxProperties properties,
|
||||||
MailboxFolderId folderId)
|
MailboxFolderId folderId) throws IOException, ApiException {
|
||||||
throws IOException, ApiException, TolerableFailureException {
|
|
||||||
String path = "/files/" + folderId;
|
String path = "/files/" + folderId;
|
||||||
Response response = sendGetRequest(properties, path);
|
Response response = sendGetRequest(properties, path);
|
||||||
if (response.code() == 404) throw new TolerableFailureException();
|
|
||||||
if (response.code() != 200) throw new ApiException();
|
if (response.code() != 200) throw new ApiException();
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
ResponseBody body = response.body();
|
||||||
@@ -247,7 +218,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (time < 1) throw new ApiException();
|
if (time < 1) throw new ApiException();
|
||||||
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
||||||
}
|
}
|
||||||
sort(list);
|
Collections.sort(list);
|
||||||
return list;
|
return list;
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
@@ -256,11 +227,9 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId, File file)
|
MailboxFileId fileId, File file) throws IOException, ApiException {
|
||||||
throws IOException, ApiException, TolerableFailureException {
|
|
||||||
String path = "/files/" + folderId + "/" + fileId;
|
String path = "/files/" + folderId + "/" + fileId;
|
||||||
Response response = sendGetRequest(properties, path);
|
Response response = sendGetRequest(properties, path);
|
||||||
if (response.code() == 404) throw new TolerableFailureException();
|
|
||||||
if (response.code() != 200) throw new ApiException();
|
if (response.code() != 200) throw new ApiException();
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
ResponseBody body = response.body();
|
||||||
@@ -276,7 +245,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
String path = "/files/" + folderId + "/" + fileId;
|
String path = "/files/" + folderId + "/" + fileId;
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.delete()
|
.delete()
|
||||||
.url(getBaseUrl(properties) + path)
|
.url(properties.getBaseUrl() + path)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
Response response = client.newCall(request).execute();
|
Response response = client.newCall(request).execute();
|
||||||
@@ -318,7 +287,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
private Response sendGetRequest(MailboxProperties properties, String path)
|
private Response sendGetRequest(MailboxProperties properties, String path)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(getBaseUrl(properties) + path)
|
.url(properties.getBaseUrl() + path)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
return client.newCall(request).execute();
|
return client.newCall(request).execute();
|
||||||
@@ -327,7 +296,7 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
private Response sendPostRequest(MailboxProperties properties, String path,
|
private Response sendPostRequest(MailboxProperties properties, String path,
|
||||||
RequestBody body) throws IOException {
|
RequestBody body) throws IOException {
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
.url(getBaseUrl(properties) + path)
|
.url(properties.getBaseUrl() + path)
|
||||||
.post(body)
|
.post(body)
|
||||||
.build();
|
.build();
|
||||||
OkHttpClient client = httpClientProvider.get();
|
OkHttpClient client = httpClientProvider.get();
|
||||||
@@ -349,7 +318,4 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
return (ArrayNode) arrayNode;
|
return (ArrayNode) arrayNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getBaseUrl(MailboxProperties properties) {
|
|
||||||
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxClient {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously starts the client.
|
|
||||||
*/
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the client and its workers, cancelling any pending tasks or
|
|
||||||
* retries.
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a contact to the client for upload.
|
|
||||||
*
|
|
||||||
* @param properties Properties for communicating with the mailbox
|
|
||||||
* managed by this client.
|
|
||||||
* @param folderId The ID of the folder to which files will be uploaded.
|
|
||||||
*/
|
|
||||||
void assignContactForUpload(ContactId c, MailboxProperties properties,
|
|
||||||
MailboxFolderId folderId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deassigns a contact from the client for upload.
|
|
||||||
*/
|
|
||||||
void deassignContactForUpload(ContactId c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a contact to the client for download.
|
|
||||||
*
|
|
||||||
* @param properties Properties for communicating with the mailbox
|
|
||||||
* managed by this client.
|
|
||||||
* @param folderId The ID of the folder from which files will be
|
|
||||||
* downloaded.
|
|
||||||
*/
|
|
||||||
void assignContactForDownload(ContactId c, MailboxProperties properties,
|
|
||||||
MailboxFolderId folderId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deassigns a contact from the client for download.
|
|
||||||
*/
|
|
||||||
void deassignContactForDownload(ContactId c);
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxClientFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a client for communicating with a contact's mailbox.
|
|
||||||
*/
|
|
||||||
MailboxClient createContactMailboxClient(
|
|
||||||
TorReachabilityMonitor reachabilityMonitor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a client for communicating with our own mailbox.
|
|
||||||
*/
|
|
||||||
MailboxClient createOwnMailboxClient(
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties);
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxClientFactoryImpl implements MailboxClientFactory {
|
|
||||||
|
|
||||||
private final MailboxWorkerFactory workerFactory;
|
|
||||||
private final TaskScheduler taskScheduler;
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final Provider<ContactMailboxConnectivityChecker>
|
|
||||||
contactCheckerProvider;
|
|
||||||
private final Provider<OwnMailboxConnectivityChecker> ownCheckerProvider;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxClientFactoryImpl(MailboxWorkerFactory workerFactory,
|
|
||||||
TaskScheduler taskScheduler,
|
|
||||||
@IoExecutor Executor ioExecutor,
|
|
||||||
Provider<ContactMailboxConnectivityChecker> contactCheckerProvider,
|
|
||||||
Provider<OwnMailboxConnectivityChecker> ownCheckerProvider) {
|
|
||||||
this.workerFactory = workerFactory;
|
|
||||||
this.taskScheduler = taskScheduler;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.contactCheckerProvider = contactCheckerProvider;
|
|
||||||
this.ownCheckerProvider = ownCheckerProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxClient createContactMailboxClient(
|
|
||||||
TorReachabilityMonitor reachabilityMonitor) {
|
|
||||||
ConnectivityChecker connectivityChecker = contactCheckerProvider.get();
|
|
||||||
return new ContactMailboxClient(workerFactory, connectivityChecker,
|
|
||||||
reachabilityMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxClient createOwnMailboxClient(
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties) {
|
|
||||||
ConnectivityChecker connectivityChecker = ownCheckerProvider.get();
|
|
||||||
return new OwnMailboxClient(workerFactory, connectivityChecker,
|
|
||||||
reachabilityMonitor, taskScheduler, ioExecutor, properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,667 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.Plugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.isClientCompatibleWithServer;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component manages a {@link MailboxClient} for each mailbox we know
|
|
||||||
* about and are able to use (our own mailbox and/or contacts' mailboxes).
|
|
||||||
* The clients are created when we come online (i.e. when the Tor plugin
|
|
||||||
* becomes {@link Plugin.State#ACTIVE active}) and destroyed when we go
|
|
||||||
* offline.
|
|
||||||
* <p/>
|
|
||||||
* The manager keeps track of the latest {@link MailboxUpdate} sent to and
|
|
||||||
* received from each contact. These updates are used to decide which
|
|
||||||
* mailboxes the manager needs clients for, and which mailbox (if any) should
|
|
||||||
* be used for uploading data to and/or downloading data from each contact.
|
|
||||||
* <p/>
|
|
||||||
* The assignments of contacts to mailboxes for upload and/or download may
|
|
||||||
* change when the following events happen:
|
|
||||||
* <ul>
|
|
||||||
* <li> A mailbox is paired or unpaired </li>
|
|
||||||
* <li> A contact is added or removed </li>
|
|
||||||
* <li> A {@link MailboxUpdate} is received from a contact </li>
|
|
||||||
* <li> We discover that our own mailbox's supported API versions have
|
|
||||||
* changed </li>
|
|
||||||
* </ul>
|
|
||||||
* <p/>
|
|
||||||
* The manager keeps its mutable state consistent with the state in the DB by
|
|
||||||
* using commit actions and events to update the manager's state on the event
|
|
||||||
* thread.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxClientManager implements Service, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(MailboxClientManager.class.getName());
|
|
||||||
|
|
||||||
private final Executor eventExecutor, dbExecutor;
|
|
||||||
private final TransactionManager db;
|
|
||||||
private final ContactManager contactManager;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
|
||||||
private final MailboxUpdateManager mailboxUpdateManager;
|
|
||||||
private final MailboxClientFactory mailboxClientFactory;
|
|
||||||
private final TorReachabilityMonitor reachabilityMonitor;
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
// All of the following mutable state must only be accessed on the
|
|
||||||
// event thread
|
|
||||||
private final Map<ContactId, Updates> contactUpdates = new HashMap<>();
|
|
||||||
private final Map<ContactId, MailboxClient> contactClients =
|
|
||||||
new HashMap<>();
|
|
||||||
@Nullable
|
|
||||||
private MailboxProperties ownProperties = null;
|
|
||||||
@Nullable
|
|
||||||
private MailboxClient ownClient = null;
|
|
||||||
private boolean online = false, handleEvents = false;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxClientManager(@EventExecutor Executor eventExecutor,
|
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
|
||||||
TransactionManager db,
|
|
||||||
ContactManager contactManager,
|
|
||||||
PluginManager pluginManager,
|
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
|
||||||
MailboxUpdateManager mailboxUpdateManager,
|
|
||||||
MailboxClientFactory mailboxClientFactory,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor) {
|
|
||||||
this.eventExecutor = eventExecutor;
|
|
||||||
this.dbExecutor = dbExecutor;
|
|
||||||
this.db = db;
|
|
||||||
this.contactManager = contactManager;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
|
||||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
|
||||||
this.mailboxClientFactory = mailboxClientFactory;
|
|
||||||
this.reachabilityMonitor = reachabilityMonitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startService() throws ServiceException {
|
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
|
||||||
reachabilityMonitor.start();
|
|
||||||
dbExecutor.execute(this::loadMailboxProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
private void loadMailboxProperties() {
|
|
||||||
LOG.info("Loading mailbox properties");
|
|
||||||
try {
|
|
||||||
db.transaction(true, txn -> {
|
|
||||||
Map<ContactId, Updates> updates = new HashMap<>();
|
|
||||||
for (Contact c : contactManager.getContacts(txn)) {
|
|
||||||
MailboxUpdate local = mailboxUpdateManager
|
|
||||||
.getLocalUpdate(txn, c.getId());
|
|
||||||
MailboxUpdate remote = mailboxUpdateManager
|
|
||||||
.getRemoteUpdate(txn, c.getId());
|
|
||||||
updates.put(c.getId(), new Updates(local, remote));
|
|
||||||
}
|
|
||||||
MailboxProperties ownProps =
|
|
||||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
|
||||||
// Use a commit action so the state in memory remains
|
|
||||||
// consistent with the state in the DB
|
|
||||||
txn.attach(() -> initialiseState(updates, ownProps));
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void initialiseState(Map<ContactId, Updates> updates,
|
|
||||||
@Nullable MailboxProperties ownProps) {
|
|
||||||
contactUpdates.putAll(updates);
|
|
||||||
ownProperties = ownProps;
|
|
||||||
Plugin tor = pluginManager.getPlugin(ID);
|
|
||||||
if (tor != null && tor.getState() == ACTIVE) {
|
|
||||||
LOG.info("Online");
|
|
||||||
online = true;
|
|
||||||
createClients();
|
|
||||||
}
|
|
||||||
// Now that the mutable state has been initialised we can start
|
|
||||||
// handling events. This is done in a commit action so that we don't
|
|
||||||
// miss any changes to the DB state or handle events for any changes
|
|
||||||
// that were already reflected in the initial load
|
|
||||||
handleEvents = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void createClients() {
|
|
||||||
LOG.info("Creating clients");
|
|
||||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates u = e.getValue();
|
|
||||||
if (isContactMailboxUsable(u.remote)) {
|
|
||||||
// Create and start a client for the contact's mailbox
|
|
||||||
MailboxClient contactClient = createAndStartClient(c);
|
|
||||||
// Assign the contact to the contact's mailbox for upload
|
|
||||||
assignContactToContactMailboxForUpload(c, contactClient, u);
|
|
||||||
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
|
|
||||||
// We don't have a usable mailbox, so assign the contact to
|
|
||||||
// the contact's mailbox for download too
|
|
||||||
assignContactToContactMailboxForDownload(c,
|
|
||||||
contactClient, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ownProperties == null) return;
|
|
||||||
if (!isOwnMailboxUsable(ownProperties)) {
|
|
||||||
LOG.warning("We have a mailbox but we can't use it");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Create and start a client for our mailbox
|
|
||||||
createAndStartClientForOwnMailbox();
|
|
||||||
// Assign contacts to our mailbox for upload/download
|
|
||||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates u = e.getValue();
|
|
||||||
if (isOwnMailboxUsable(ownProperties, u.remote)) {
|
|
||||||
// Assign the contact to our mailbox for download
|
|
||||||
assignContactToOwnMailboxForDownload(c, u);
|
|
||||||
if (!isContactMailboxUsable(u.remote)) {
|
|
||||||
// The contact doesn't have a usable mailbox, so assign
|
|
||||||
// the contact to our mailbox for upload too
|
|
||||||
assignContactToOwnMailboxForUpload(c, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopService() throws ServiceException {
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
eventExecutor.execute(() -> {
|
|
||||||
handleEvents = false;
|
|
||||||
if (online) destroyClients();
|
|
||||||
latch.countDown();
|
|
||||||
});
|
|
||||||
reachabilityMonitor.destroy();
|
|
||||||
try {
|
|
||||||
latch.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new ServiceException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void destroyClients() {
|
|
||||||
LOG.info("Destroying clients");
|
|
||||||
for (MailboxClient client : contactClients.values()) {
|
|
||||||
client.destroy();
|
|
||||||
}
|
|
||||||
contactClients.clear();
|
|
||||||
destroyOwnClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void destroyOwnClient() {
|
|
||||||
if (ownClient != null) {
|
|
||||||
ownClient.destroy();
|
|
||||||
ownClient = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (!handleEvents) return;
|
|
||||||
if (e instanceof TransportActiveEvent) {
|
|
||||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
|
||||||
if (t.getTransportId().equals(ID)) onTorActive();
|
|
||||||
} else if (e instanceof TransportInactiveEvent) {
|
|
||||||
TransportInactiveEvent t = (TransportInactiveEvent) e;
|
|
||||||
if (t.getTransportId().equals(ID)) onTorInactive();
|
|
||||||
} else if (e instanceof MailboxPairedEvent) {
|
|
||||||
LOG.info("Mailbox paired");
|
|
||||||
MailboxPairedEvent m = (MailboxPairedEvent) e;
|
|
||||||
onMailboxPaired(m.getProperties(), m.getLocalUpdates());
|
|
||||||
} else if (e instanceof MailboxUnpairedEvent) {
|
|
||||||
LOG.info("Mailbox unpaired");
|
|
||||||
MailboxUnpairedEvent m = (MailboxUnpairedEvent) e;
|
|
||||||
onMailboxUnpaired(m.getLocalUpdates());
|
|
||||||
} else if (e instanceof MailboxUpdateSentToNewContactEvent) {
|
|
||||||
LOG.info("Contact added");
|
|
||||||
MailboxUpdateSentToNewContactEvent
|
|
||||||
m = (MailboxUpdateSentToNewContactEvent) e;
|
|
||||||
onContactAdded(m.getContactId(), m.getMailboxUpdate());
|
|
||||||
} else if (e instanceof ContactRemovedEvent) {
|
|
||||||
LOG.info("Contact removed");
|
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
|
||||||
onContactRemoved(c.getContactId());
|
|
||||||
} else if (e instanceof RemoteMailboxUpdateEvent) {
|
|
||||||
LOG.info("Remote mailbox update");
|
|
||||||
RemoteMailboxUpdateEvent r = (RemoteMailboxUpdateEvent) e;
|
|
||||||
onRemoteMailboxUpdate(r.getContact(), r.getMailboxUpdate());
|
|
||||||
} else if (e instanceof OwnMailboxConnectionStatusEvent) {
|
|
||||||
OwnMailboxConnectionStatusEvent o =
|
|
||||||
(OwnMailboxConnectionStatusEvent) e;
|
|
||||||
onOwnMailboxConnectionStatusChanged(o.getStatus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onTorActive() {
|
|
||||||
// If we checked the plugin at startup concurrently with the plugin
|
|
||||||
// becoming active then `online` may already be true when we receive
|
|
||||||
// the first TransportActiveEvent, in which case ignore it
|
|
||||||
if (online) return;
|
|
||||||
LOG.info("Online");
|
|
||||||
online = true;
|
|
||||||
createClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onTorInactive() {
|
|
||||||
// If we checked the plugin at startup concurrently with the plugin
|
|
||||||
// becoming inactive then `online` may already be false when we
|
|
||||||
// receive the first TransportInactiveEvent, in which case ignore it
|
|
||||||
if (!online) return;
|
|
||||||
LOG.info("Offline");
|
|
||||||
online = false;
|
|
||||||
destroyClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onMailboxPaired(MailboxProperties ownProps,
|
|
||||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
|
|
||||||
for (Entry<ContactId, MailboxUpdateWithMailbox> e :
|
|
||||||
localUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates u = contactUpdates.get(c);
|
|
||||||
contactUpdates.put(c, new Updates(e.getValue(), u.remote));
|
|
||||||
}
|
|
||||||
ownProperties = ownProps;
|
|
||||||
if (!online) return;
|
|
||||||
if (!isOwnMailboxUsable(ownProperties)) {
|
|
||||||
LOG.warning("We have a mailbox but we can't use it");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Create and start a client for our mailbox
|
|
||||||
createAndStartClientForOwnMailbox();
|
|
||||||
// Assign contacts to our mailbox for upload/download
|
|
||||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates u = e.getValue();
|
|
||||||
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
|
|
||||||
// Our mailbox isn't usable for communicating with this
|
|
||||||
// contact, so don't assign/reassign this contact
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isContactMailboxUsable(u.remote)) {
|
|
||||||
// The contact has a usable mailbox, so the contact should
|
|
||||||
// currently be assigned to the contact's mailbox for upload
|
|
||||||
// and download. Reassign the contact to our mailbox for
|
|
||||||
// download
|
|
||||||
MailboxClient contactClient =
|
|
||||||
requireNonNull(contactClients.get(c));
|
|
||||||
contactClient.deassignContactForDownload(c);
|
|
||||||
} else {
|
|
||||||
// The contact doesn't have a usable mailbox, so assign the
|
|
||||||
// contact to our mailbox for upload
|
|
||||||
assignContactToOwnMailboxForUpload(c, u);
|
|
||||||
}
|
|
||||||
assignContactToOwnMailboxForDownload(c, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onMailboxUnpaired(Map<ContactId, MailboxUpdate> localUpdates) {
|
|
||||||
for (Entry<ContactId, MailboxUpdate> e : localUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates updates = contactUpdates.get(c);
|
|
||||||
contactUpdates.put(c, new Updates(e.getValue(), updates.remote));
|
|
||||||
}
|
|
||||||
MailboxProperties oldOwnProperties = ownProperties;
|
|
||||||
ownProperties = null;
|
|
||||||
if (!online) return;
|
|
||||||
// Destroy the client for our own mailbox, if any
|
|
||||||
destroyOwnClient();
|
|
||||||
// Reassign contacts to their own mailboxes for download where possible
|
|
||||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
|
||||||
ContactId c = e.getKey();
|
|
||||||
Updates u = e.getValue();
|
|
||||||
if (isContactMailboxUsable(u.remote) &&
|
|
||||||
isOwnMailboxUsable(oldOwnProperties, u.remote)) {
|
|
||||||
// The contact should currently be assigned to our mailbox
|
|
||||||
// for download. Reassign the contact to the contact's
|
|
||||||
// mailbox for download
|
|
||||||
MailboxClient contactClient =
|
|
||||||
requireNonNull(contactClients.get(c));
|
|
||||||
assignContactToContactMailboxForDownload(c, contactClient, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactAdded(ContactId c, MailboxUpdate u) {
|
|
||||||
Updates old = contactUpdates.put(c, new Updates(u, null));
|
|
||||||
if (old != null) throw new IllegalStateException();
|
|
||||||
// We haven't yet received an update from the newly added contact,
|
|
||||||
// so at this stage we don't need to assign the contact to any
|
|
||||||
// mailboxes for upload or download
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactRemoved(ContactId c) {
|
|
||||||
Updates updates = requireNonNull(contactUpdates.remove(c));
|
|
||||||
if (!online) return;
|
|
||||||
// Destroy the client for the contact's mailbox, if any
|
|
||||||
MailboxClient client = contactClients.remove(c);
|
|
||||||
if (client != null) client.destroy();
|
|
||||||
// If we have a mailbox and the contact is assigned to it for upload
|
|
||||||
// and/or download, deassign the contact
|
|
||||||
if (ownProperties == null) return;
|
|
||||||
if (isOwnMailboxUsable(ownProperties, updates.remote)) {
|
|
||||||
// We have a usable mailbox, so the contact should currently be
|
|
||||||
// assigned to our mailbox for download. Deassign the contact from
|
|
||||||
// our mailbox for download
|
|
||||||
requireNonNull(ownClient).deassignContactForDownload(c);
|
|
||||||
if (!isContactMailboxUsable(updates.remote)) {
|
|
||||||
// The contact doesn't have a usable mailbox, so the contact
|
|
||||||
// should currently be assigned to our mailbox for upload.
|
|
||||||
// Deassign the contact from our mailbox for upload
|
|
||||||
requireNonNull(ownClient).deassignContactForUpload(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onRemoteMailboxUpdate(ContactId c, MailboxUpdate remote) {
|
|
||||||
Updates old = contactUpdates.get(c);
|
|
||||||
MailboxUpdate oldRemote = old.remote;
|
|
||||||
Updates u = new Updates(old.local, remote);
|
|
||||||
contactUpdates.put(c, u);
|
|
||||||
if (!online) return;
|
|
||||||
// What may have changed?
|
|
||||||
// * Contact's mailbox may be usable now, was unusable before
|
|
||||||
// * Contact's mailbox may be unusable now, was usable before
|
|
||||||
// * Contact's mailbox may have been replaced
|
|
||||||
// * Contact's mailbox may have changed its API versions
|
|
||||||
// * Contact may be able to use our mailbox now, was unable before
|
|
||||||
// * Contact may be unable to use our mailbox now, was able before
|
|
||||||
boolean wasContactMailboxUsable = isContactMailboxUsable(oldRemote);
|
|
||||||
boolean isContactMailboxUsable = isContactMailboxUsable(remote);
|
|
||||||
boolean wasOwnMailboxUsable =
|
|
||||||
isOwnMailboxUsable(ownProperties, oldRemote);
|
|
||||||
boolean isOwnMailboxUsable = isOwnMailboxUsable(ownProperties, remote);
|
|
||||||
|
|
||||||
// Create/destroy/replace the client for the contact's mailbox if needed
|
|
||||||
MailboxClient contactClient = null;
|
|
||||||
boolean clientReplaced = false;
|
|
||||||
if (isContactMailboxUsable) {
|
|
||||||
if (wasContactMailboxUsable) {
|
|
||||||
MailboxProperties oldProps = getMailboxProperties(oldRemote);
|
|
||||||
MailboxProperties newProps = getMailboxProperties(remote);
|
|
||||||
if (oldProps.equals(newProps)) {
|
|
||||||
// The contact previously had a usable mailbox, now has
|
|
||||||
// a usable mailbox, it's the same mailbox, and its API
|
|
||||||
// versions haven't changed. Keep using the existing client
|
|
||||||
contactClient = requireNonNull(contactClients.get(c));
|
|
||||||
} else {
|
|
||||||
// The contact previously had a usable mailbox and now has
|
|
||||||
// a usable mailbox, but either it's a new mailbox or its
|
|
||||||
// API versions have changed. Replace the client
|
|
||||||
requireNonNull(contactClients.remove(c)).destroy();
|
|
||||||
contactClient = createAndStartClient(c);
|
|
||||||
clientReplaced = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The client didn't previously have a usable mailbox but now
|
|
||||||
// has one. Create and start a client
|
|
||||||
contactClient = createAndStartClient(c);
|
|
||||||
}
|
|
||||||
} else if (wasContactMailboxUsable) {
|
|
||||||
// The client previously had a usable mailbox but no longer does.
|
|
||||||
// Destroy the existing client
|
|
||||||
requireNonNull(contactClients.remove(c)).destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean wasAssignedToOwnMailboxForUpload =
|
|
||||||
wasOwnMailboxUsable && !wasContactMailboxUsable;
|
|
||||||
boolean willBeAssignedToOwnMailboxForUpload =
|
|
||||||
isOwnMailboxUsable && !isContactMailboxUsable;
|
|
||||||
|
|
||||||
boolean wasAssignedToContactMailboxForDownload =
|
|
||||||
!wasOwnMailboxUsable && wasContactMailboxUsable;
|
|
||||||
boolean willBeAssignedToContactMailboxForDownload =
|
|
||||||
!isOwnMailboxUsable && isContactMailboxUsable;
|
|
||||||
|
|
||||||
// Deassign the contact for upload/download if needed
|
|
||||||
if (wasAssignedToOwnMailboxForUpload &&
|
|
||||||
!willBeAssignedToOwnMailboxForUpload) {
|
|
||||||
requireNonNull(ownClient).deassignContactForUpload(c);
|
|
||||||
}
|
|
||||||
if (wasOwnMailboxUsable && !isOwnMailboxUsable) {
|
|
||||||
requireNonNull(ownClient).deassignContactForDownload(c);
|
|
||||||
}
|
|
||||||
// If the client for the contact's mailbox was replaced or destroyed
|
|
||||||
// above then we don't need to deassign the contact for download
|
|
||||||
if (wasAssignedToContactMailboxForDownload &&
|
|
||||||
!willBeAssignedToContactMailboxForDownload &&
|
|
||||||
!clientReplaced && isContactMailboxUsable) {
|
|
||||||
requireNonNull(contactClient).deassignContactForDownload(c);
|
|
||||||
}
|
|
||||||
// We never need to deassign the contact from the contact's mailbox for
|
|
||||||
// upload: this would only be needed if the contact's mailbox were no
|
|
||||||
// longer usable, in which case the client would already have been
|
|
||||||
// destroyed above. Thanks to the linter for spotting this
|
|
||||||
|
|
||||||
// Assign the contact for upload/download if needed
|
|
||||||
if (!wasAssignedToOwnMailboxForUpload &&
|
|
||||||
willBeAssignedToOwnMailboxForUpload) {
|
|
||||||
assignContactToOwnMailboxForUpload(c, u);
|
|
||||||
}
|
|
||||||
if (!wasOwnMailboxUsable && isOwnMailboxUsable) {
|
|
||||||
assignContactToOwnMailboxForDownload(c, u);
|
|
||||||
}
|
|
||||||
if ((!wasContactMailboxUsable || clientReplaced) &&
|
|
||||||
isContactMailboxUsable) {
|
|
||||||
assignContactToContactMailboxForUpload(c, contactClient, u);
|
|
||||||
}
|
|
||||||
if ((!wasAssignedToContactMailboxForDownload || clientReplaced) &&
|
|
||||||
willBeAssignedToContactMailboxForDownload) {
|
|
||||||
assignContactToContactMailboxForDownload(c, contactClient, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onOwnMailboxConnectionStatusChanged(MailboxStatus status) {
|
|
||||||
if (!online || ownProperties == null) return;
|
|
||||||
List<MailboxVersion> oldServerSupports =
|
|
||||||
ownProperties.getServerSupports();
|
|
||||||
List<MailboxVersion> newServerSupports = status.getServerSupports();
|
|
||||||
if (!oldServerSupports.equals(newServerSupports)) {
|
|
||||||
LOG.info("Our mailbox's supported API versions have changed");
|
|
||||||
// This potentially affects every assignment of contacts to
|
|
||||||
// mailboxes for upload and download, so just rebuild the clients
|
|
||||||
// and assignments from scratch
|
|
||||||
destroyClients();
|
|
||||||
createClients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void createAndStartClientForOwnMailbox() {
|
|
||||||
if (ownClient != null) throw new IllegalStateException();
|
|
||||||
ownClient = mailboxClientFactory.createOwnMailboxClient(
|
|
||||||
reachabilityMonitor, requireNonNull(ownProperties));
|
|
||||||
ownClient.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private MailboxClient createAndStartClient(ContactId c) {
|
|
||||||
MailboxClient client = mailboxClientFactory
|
|
||||||
.createContactMailboxClient(reachabilityMonitor);
|
|
||||||
MailboxClient old = contactClients.put(c, client);
|
|
||||||
if (old != null) throw new IllegalStateException();
|
|
||||||
client.start();
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void assignContactToOwnMailboxForDownload(ContactId c, Updates u) {
|
|
||||||
MailboxProperties localProps = getMailboxProperties(u.local);
|
|
||||||
requireNonNull(ownClient).assignContactForDownload(c,
|
|
||||||
requireNonNull(ownProperties),
|
|
||||||
requireNonNull(localProps.getOutboxId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void assignContactToOwnMailboxForUpload(ContactId c, Updates u) {
|
|
||||||
MailboxProperties localProps = getMailboxProperties(u.local);
|
|
||||||
requireNonNull(ownClient).assignContactForUpload(c,
|
|
||||||
requireNonNull(ownProperties),
|
|
||||||
requireNonNull(localProps.getInboxId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void assignContactToContactMailboxForDownload(ContactId c,
|
|
||||||
MailboxClient contactClient, Updates u) {
|
|
||||||
MailboxProperties remoteProps =
|
|
||||||
getMailboxProperties(requireNonNull(u.remote));
|
|
||||||
contactClient.assignContactForDownload(c, remoteProps,
|
|
||||||
requireNonNull(remoteProps.getInboxId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void assignContactToContactMailboxForUpload(ContactId c,
|
|
||||||
MailboxClient contactClient, Updates u) {
|
|
||||||
MailboxProperties remoteProps =
|
|
||||||
getMailboxProperties(requireNonNull(u.remote));
|
|
||||||
contactClient.assignContactForUpload(c, remoteProps,
|
|
||||||
requireNonNull(remoteProps.getOutboxId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link MailboxProperties} included in the given update,
|
|
||||||
* which must be a {@link MailboxUpdateWithMailbox}.
|
|
||||||
*/
|
|
||||||
private MailboxProperties getMailboxProperties(MailboxUpdate update) {
|
|
||||||
if (!(update instanceof MailboxUpdateWithMailbox)) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
MailboxUpdateWithMailbox mailbox = (MailboxUpdateWithMailbox) update;
|
|
||||||
return mailbox.getMailboxProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we can use our own mailbox to communicate with the
|
|
||||||
* contact that sent the given update.
|
|
||||||
*/
|
|
||||||
private boolean isOwnMailboxUsable(
|
|
||||||
@Nullable MailboxProperties ownProperties,
|
|
||||||
@Nullable MailboxUpdate remote) {
|
|
||||||
if (ownProperties == null || remote == null) return false;
|
|
||||||
return isMailboxUsable(remote.getClientSupports(),
|
|
||||||
ownProperties.getServerSupports());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we can use the contact's mailbox to communicate with
|
|
||||||
* the contact that sent the given update.
|
|
||||||
*/
|
|
||||||
private boolean isContactMailboxUsable(@Nullable MailboxUpdate remote) {
|
|
||||||
if (remote instanceof MailboxUpdateWithMailbox) {
|
|
||||||
MailboxUpdateWithMailbox remoteMailbox =
|
|
||||||
(MailboxUpdateWithMailbox) remote;
|
|
||||||
return isMailboxUsable(remoteMailbox.getClientSupports(),
|
|
||||||
remoteMailbox.getMailboxProperties().getServerSupports());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if we can communicate with a contact that has the given
|
|
||||||
* client-supported API versions via a mailbox with the given
|
|
||||||
* server-supported API versions.
|
|
||||||
*/
|
|
||||||
private boolean isMailboxUsable(List<MailboxVersion> contactClient,
|
|
||||||
List<MailboxVersion> server) {
|
|
||||||
return isClientCompatibleWithServer(contactClient, server)
|
|
||||||
&& isClientCompatibleWithServer(CLIENT_SUPPORTS, server);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if our client-supported API versions are compatible with
|
|
||||||
* our own mailbox's server-supported API versions.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
|
||||||
private boolean isOwnMailboxUsable(MailboxProperties ownProperties) {
|
|
||||||
return isClientCompatibleWithServer(CLIENT_SUPPORTS,
|
|
||||||
ownProperties.getServerSupports());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A container for the latest {@link MailboxUpdate updates} sent to and
|
|
||||||
* received from a given contact.
|
|
||||||
*/
|
|
||||||
private static class Updates {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The latest update sent to the contact.
|
|
||||||
*/
|
|
||||||
private final MailboxUpdate local;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The latest update received from the contact, or null if no update
|
|
||||||
* has been received.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private final MailboxUpdate remote;
|
|
||||||
|
|
||||||
private Updates(MailboxUpdate local, @Nullable MailboxUpdate remote) {
|
|
||||||
this.local = local;
|
|
||||||
this.remote = remote;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
abstract class MailboxDownloadWorker implements MailboxWorker,
|
|
||||||
ConnectivityObserver, TorReachabilityObserver {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the worker is started it waits for a connectivity check, then
|
|
||||||
* starts its first download cycle: checking for files to download,
|
|
||||||
* downloading and deleting the files, and checking again until all files
|
|
||||||
* have been downloaded and deleted.
|
|
||||||
* <p>
|
|
||||||
* The worker then waits for our Tor hidden service to be reachable before
|
|
||||||
* starting its second download cycle. This ensures that if a contact
|
|
||||||
* tried and failed to connect to our hidden service before it was
|
|
||||||
* reachable, and therefore uploaded a file to the mailbox instead, we'll
|
|
||||||
* find the file in the second download cycle.
|
|
||||||
*/
|
|
||||||
protected enum State {
|
|
||||||
CREATED,
|
|
||||||
CONNECTIVITY_CHECK,
|
|
||||||
DOWNLOAD_CYCLE_1,
|
|
||||||
WAITING_FOR_TOR,
|
|
||||||
DOWNLOAD_CYCLE_2,
|
|
||||||
FINISHED,
|
|
||||||
DESTROYED
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static final Logger LOG =
|
|
||||||
getLogger(MailboxDownloadWorker.class.getName());
|
|
||||||
|
|
||||||
private final ConnectivityChecker connectivityChecker;
|
|
||||||
private final TorReachabilityMonitor torReachabilityMonitor;
|
|
||||||
protected final MailboxApiCaller mailboxApiCaller;
|
|
||||||
protected final MailboxApi mailboxApi;
|
|
||||||
private final MailboxFileManager mailboxFileManager;
|
|
||||||
protected final MailboxProperties mailboxProperties;
|
|
||||||
protected final Object lock = new Object();
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
protected State state = State.CREATED;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
protected Cancellable apiCall = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the API call that starts the worker's download cycle.
|
|
||||||
*/
|
|
||||||
protected abstract ApiCall createApiCallForDownloadCycle();
|
|
||||||
|
|
||||||
MailboxDownloadWorker(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor torReachabilityMonitor,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxFileManager mailboxFileManager,
|
|
||||||
MailboxProperties mailboxProperties) {
|
|
||||||
this.connectivityChecker = connectivityChecker;
|
|
||||||
this.torReachabilityMonitor = torReachabilityMonitor;
|
|
||||||
this.mailboxApiCaller = mailboxApiCaller;
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
this.mailboxFileManager = mailboxFileManager;
|
|
||||||
this.mailboxProperties = mailboxProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
LOG.info("Started");
|
|
||||||
synchronized (lock) {
|
|
||||||
// Don't allow the worker to be reused
|
|
||||||
if (state != State.CREATED) return;
|
|
||||||
state = State.CONNECTIVITY_CHECK;
|
|
||||||
}
|
|
||||||
// Avoid leaking observer in case destroy() is called concurrently
|
|
||||||
// before observer is added
|
|
||||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
|
||||||
boolean destroyed;
|
|
||||||
synchronized (lock) {
|
|
||||||
destroyed = state == State.DESTROYED;
|
|
||||||
}
|
|
||||||
if (destroyed) connectivityChecker.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
LOG.info("Destroyed");
|
|
||||||
Cancellable apiCall;
|
|
||||||
synchronized (lock) {
|
|
||||||
state = State.DESTROYED;
|
|
||||||
apiCall = this.apiCall;
|
|
||||||
this.apiCall = null;
|
|
||||||
}
|
|
||||||
if (apiCall != null) apiCall.cancel();
|
|
||||||
connectivityChecker.removeObserver(this);
|
|
||||||
torReachabilityMonitor.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectivityCheckSucceeded() {
|
|
||||||
LOG.info("Connectivity check succeeded");
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CONNECTIVITY_CHECK) return;
|
|
||||||
state = State.DOWNLOAD_CYCLE_1;
|
|
||||||
// Start first download cycle
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
createApiCallForDownloadCycle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDownloadCycleFinished() {
|
|
||||||
boolean addObserver = false;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DOWNLOAD_CYCLE_1) {
|
|
||||||
LOG.info("First download cycle finished");
|
|
||||||
state = State.WAITING_FOR_TOR;
|
|
||||||
apiCall = null;
|
|
||||||
addObserver = true;
|
|
||||||
} else if (state == State.DOWNLOAD_CYCLE_2) {
|
|
||||||
LOG.info("Second download cycle finished");
|
|
||||||
state = State.FINISHED;
|
|
||||||
apiCall = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (addObserver) {
|
|
||||||
// Avoid leaking observer in case destroy() is called concurrently
|
|
||||||
// before observer is added
|
|
||||||
torReachabilityMonitor.addOneShotObserver(this);
|
|
||||||
boolean destroyed;
|
|
||||||
synchronized (lock) {
|
|
||||||
destroyed = state == State.DESTROYED;
|
|
||||||
}
|
|
||||||
if (destroyed) torReachabilityMonitor.removeObserver(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void downloadNextFile(Queue<FolderFile> queue) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
if (queue.isEmpty()) {
|
|
||||||
// Check for files again, as new files may have arrived while
|
|
||||||
// we were downloading
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
createApiCallForDownloadCycle());
|
|
||||||
} else {
|
|
||||||
FolderFile file = queue.remove();
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
new SimpleApiCall(() ->
|
|
||||||
apiCallDownloadFile(file, queue)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apiCallDownloadFile(FolderFile file, Queue<FolderFile> queue)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
LOG.info("Downloading file");
|
|
||||||
File tempFile = mailboxFileManager.createTempFileForDownload();
|
|
||||||
try {
|
|
||||||
mailboxApi.getFile(mailboxProperties, file.folderId, file.fileId,
|
|
||||||
tempFile);
|
|
||||||
} catch (IOException | ApiException e) {
|
|
||||||
if (!tempFile.delete()) {
|
|
||||||
LOG.warning("Failed to delete temporary file");
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
// File not found - continue to the next file
|
|
||||||
LOG.warning("File does not exist");
|
|
||||||
if (!tempFile.delete()) {
|
|
||||||
LOG.warning("Failed to delete temporary file");
|
|
||||||
}
|
|
||||||
downloadNextFile(queue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mailboxFileManager.handleDownloadedFile(tempFile);
|
|
||||||
deleteFile(file, queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteFile(FolderFile file, Queue<FolderFile> queue) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apiCallDeleteFile(FolderFile file, Queue<FolderFile> queue)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mailboxApi.deleteFile(mailboxProperties, file.folderId,
|
|
||||||
file.fileId);
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
// File not found - continue to the next file
|
|
||||||
LOG.warning("File does not exist");
|
|
||||||
}
|
|
||||||
downloadNextFile(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTorReachable() {
|
|
||||||
LOG.info("Our Tor hidden service is reachable");
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WAITING_FOR_TOR) return;
|
|
||||||
state = State.DOWNLOAD_CYCLE_2;
|
|
||||||
// Start second download cycle
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
createApiCallForDownloadCycle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package access for testing
|
|
||||||
static class FolderFile {
|
|
||||||
|
|
||||||
final MailboxFolderId folderId;
|
|
||||||
final MailboxFileId fileId;
|
|
||||||
|
|
||||||
FolderFile(MailboxFolderId folderId, MailboxFileId fileId) {
|
|
||||||
this.folderId = folderId;
|
|
||||||
this.fileId = fileId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxFileManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an empty file for storing a download.
|
|
||||||
*/
|
|
||||||
File createTempFileForDownload() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a file to be uploaded to the given contact and writes any
|
|
||||||
* waiting data to the file. The IDs of any messages sent or acked will
|
|
||||||
* be added to the given {@link OutgoingSessionRecord}.
|
|
||||||
*/
|
|
||||||
File createAndWriteTempFileForUpload(ContactId contactId,
|
|
||||||
OutgoingSessionRecord sessionRecord) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a file that has been downloaded. The file should be created
|
|
||||||
* with {@link #createTempFileForDownload()}.
|
|
||||||
*/
|
|
||||||
void handleDownloadedFile(File f);
|
|
||||||
}
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
|
||||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.delete;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(MailboxFileManagerImpl.class.getName());
|
|
||||||
|
|
||||||
// Package access for testing
|
|
||||||
static final String DOWNLOAD_DIR_NAME = "downloads";
|
|
||||||
static final String UPLOAD_DIR_NAME = "uploads";
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
private final ConnectionManager connectionManager;
|
|
||||||
private final LifecycleManager lifecycleManager;
|
|
||||||
private final File mailboxDir;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final CountDownLatch orphanLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxFileManagerImpl(@IoExecutor Executor ioExecutor,
|
|
||||||
PluginManager pluginManager,
|
|
||||||
ConnectionManager connectionManager,
|
|
||||||
LifecycleManager lifecycleManager,
|
|
||||||
@MailboxDirectory File mailboxDir,
|
|
||||||
EventBus eventBus) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.lifecycleManager = lifecycleManager;
|
|
||||||
this.mailboxDir = mailboxDir;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File createTempFileForDownload() throws IOException {
|
|
||||||
return createTempFile(DOWNLOAD_DIR_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File createAndWriteTempFileForUpload(ContactId contactId,
|
|
||||||
OutgoingSessionRecord sessionRecord) throws IOException {
|
|
||||||
File f = createTempFile(UPLOAD_DIR_NAME);
|
|
||||||
// We shouldn't reach this point until the plugin has been started
|
|
||||||
SimplexPlugin plugin =
|
|
||||||
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_PATH, f.getAbsolutePath());
|
|
||||||
TransportConnectionWriter writer = plugin.createWriter(p);
|
|
||||||
if (writer == null) {
|
|
||||||
delete(f);
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
MailboxFileWriter decorated = new MailboxFileWriter(writer);
|
|
||||||
LOG.info("Writing file for upload");
|
|
||||||
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
|
|
||||||
sessionRecord);
|
|
||||||
if (decorated.awaitDisposal()) {
|
|
||||||
// An exception was thrown during the session - delete the file
|
|
||||||
delete(f);
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File createTempFile(String dirName) throws IOException {
|
|
||||||
// Wait for orphaned files to be handled before creating new files
|
|
||||||
try {
|
|
||||||
orphanLatch.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
File dir = createDirectoryIfNeeded(dirName);
|
|
||||||
return File.createTempFile("mailbox", ".tmp", dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
private File createDirectoryIfNeeded(String name) throws IOException {
|
|
||||||
File dir = new File(mailboxDir, name);
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
dir.mkdirs();
|
|
||||||
if (!dir.isDirectory()) {
|
|
||||||
throw new IOException("Failed to create directory '" + name + "'");
|
|
||||||
}
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleDownloadedFile(File f) {
|
|
||||||
// We shouldn't reach this point until the plugin has been started
|
|
||||||
SimplexPlugin plugin =
|
|
||||||
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_PATH, f.getAbsolutePath());
|
|
||||||
TransportConnectionReader reader = plugin.createReader(p);
|
|
||||||
if (reader == null) {
|
|
||||||
LOG.warning("Failed to create reader for downloaded file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TransportConnectionReader decorated = new MailboxFileReader(reader, f);
|
|
||||||
LOG.info("Reading downloaded file");
|
|
||||||
connectionManager.manageIncomingConnection(ID, decorated,
|
|
||||||
exception -> isHandlingComplete(exception, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHandlingComplete(boolean exception, boolean recognised) {
|
|
||||||
// If we've successfully read the file then we're done
|
|
||||||
if (!exception && recognised) return true;
|
|
||||||
// If the app is shutting down we may get spurious IO exceptions
|
|
||||||
// due to executors being shut down. Leave the file in the download
|
|
||||||
// directory and we'll try to read it again at the next startup
|
|
||||||
return !lifecycleManager.getLifecycleState().isAfter(RUNNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
// Wait for the transport to become active before handling orphaned
|
|
||||||
// files so that we can get the plugin from the plugin manager
|
|
||||||
if (e instanceof TransportActiveEvent) {
|
|
||||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
|
||||||
if (t.getTransportId().equals(ID)) {
|
|
||||||
ioExecutor.execute(this::handleOrphanedFiles);
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called at startup, as soon as the plugin is started, to
|
|
||||||
* delete any files that were left in the upload directory at the last
|
|
||||||
* shutdown and handle any files that were left in the download directory.
|
|
||||||
*/
|
|
||||||
@IoExecutor
|
|
||||||
private void handleOrphanedFiles() {
|
|
||||||
try {
|
|
||||||
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
|
|
||||||
File[] orphanedUploads = uploadDir.listFiles();
|
|
||||||
if (orphanedUploads != null) {
|
|
||||||
for (File f : orphanedUploads) delete(f);
|
|
||||||
}
|
|
||||||
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
|
||||||
File[] orphanedDownloads = downloadDir.listFiles();
|
|
||||||
// Now that we've got the list of orphaned downloads, new files
|
|
||||||
// can be created in the download directory
|
|
||||||
orphanLatch.countDown();
|
|
||||||
if (orphanedDownloads != null) {
|
|
||||||
for (File f : orphanedDownloads) handleDownloadedFile(f);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MailboxFileReader implements TransportConnectionReader {
|
|
||||||
|
|
||||||
private final TransportConnectionReader delegate;
|
|
||||||
private final File file;
|
|
||||||
|
|
||||||
private MailboxFileReader(TransportConnectionReader delegate,
|
|
||||||
File file) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() throws IOException {
|
|
||||||
return delegate.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose(boolean exception, boolean recognised)
|
|
||||||
throws IOException {
|
|
||||||
delegate.dispose(exception, recognised);
|
|
||||||
if (isHandlingComplete(exception, recognised)) {
|
|
||||||
LOG.info("Deleting downloaded file");
|
|
||||||
delete(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MailboxFileWriter
|
|
||||||
implements TransportConnectionWriter {
|
|
||||||
|
|
||||||
private final TransportConnectionWriter delegate;
|
|
||||||
private final BlockingQueue<Boolean> disposalResult =
|
|
||||||
new ArrayBlockingQueue<>(1);
|
|
||||||
|
|
||||||
private MailboxFileWriter(TransportConnectionWriter delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaxLatency() {
|
|
||||||
return delegate.getMaxLatency();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
return delegate.getMaxIdleTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return delegate.isLossyAndCheap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getOutputStream() throws IOException {
|
|
||||||
return delegate.getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose(boolean exception) throws IOException {
|
|
||||||
delegate.dispose(exception);
|
|
||||||
disposalResult.add(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for the delegate to be disposed and returns true if an
|
|
||||||
* exception occurred.
|
|
||||||
*/
|
|
||||||
private boolean awaitDisposal() {
|
|
||||||
try {
|
|
||||||
return disposalResult.take();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while waiting for disposal");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,10 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -100,60 +98,27 @@ class MailboxManagerImpl implements MailboxManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkConnection() {
|
public boolean checkConnection() {
|
||||||
List<MailboxVersion> versions = null;
|
boolean success;
|
||||||
try {
|
try {
|
||||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||||
mailboxSettingsManager::getOwnMailboxProperties);
|
mailboxSettingsManager::getOwnMailboxProperties);
|
||||||
if (props == null) throw new DbException();
|
success = api.checkStatus(props);
|
||||||
versions = api.getServerSupports(props);
|
} catch (DbException | IOException | MailboxApi.ApiException e) {
|
||||||
} catch (DbException e) {
|
success = false;
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
// we don't treat this is a failure to record
|
|
||||||
return false;
|
|
||||||
} catch (IOException | MailboxApi.ApiException e) {
|
|
||||||
// we record this as a failure
|
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
try {
|
if (success) {
|
||||||
recordCheckResult(versions);
|
try {
|
||||||
} catch (DbException e) {
|
// we are only recording successful connections here
|
||||||
logException(LOG, WARNING, e);
|
// as those update the UI and failures might be false negatives
|
||||||
}
|
db.transaction(false, txn ->
|
||||||
return versions != null;
|
mailboxSettingsManager.recordSuccessfulConnection(txn,
|
||||||
}
|
clock.currentTimeMillis()));
|
||||||
|
} catch (DbException e) {
|
||||||
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
|
logException(LOG, WARNING, e);
|
||||||
throws DbException {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
db.transaction(false, txn -> {
|
|
||||||
if (versions != null) {
|
|
||||||
mailboxSettingsManager
|
|
||||||
.recordSuccessfulConnection(txn, now, versions);
|
|
||||||
} else {
|
|
||||||
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean unPair() throws DbException {
|
|
||||||
MailboxProperties properties = db.transactionWithNullableResult(true,
|
|
||||||
mailboxSettingsManager::getOwnMailboxProperties);
|
|
||||||
if (properties == null) {
|
|
||||||
// no more mailbox, that's strange but possible if called in quick
|
|
||||||
// succession, so let's return true this time
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
boolean wasWiped;
|
|
||||||
try {
|
|
||||||
api.wipeMailbox(properties);
|
|
||||||
wasWiped = true;
|
|
||||||
} catch (IOException | MailboxApi.ApiException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
wasWiped = false;
|
|
||||||
}
|
|
||||||
db.transaction(false,
|
|
||||||
mailboxSettingsManager::removeOwnMailboxProperties);
|
|
||||||
return wasWiped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,34 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FeatureFlags;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MailboxModule {
|
public class MailboxModule {
|
||||||
|
|
||||||
public static class EagerSingletons {
|
public static class EagerSingletons {
|
||||||
@Inject
|
@Inject
|
||||||
MailboxUpdateValidator mailboxUpdateValidator;
|
MailboxPropertyValidator mailboxPropertyValidator;
|
||||||
@Inject
|
@Inject
|
||||||
MailboxUpdateManager mailboxUpdateManager;
|
MailboxPropertyManager mailboxPropertyManager;
|
||||||
@Inject
|
|
||||||
MailboxFileManager mailboxFileManager;
|
|
||||||
@Inject
|
|
||||||
MailboxClientManager mailboxClientManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -59,123 +44,43 @@ public class MailboxModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
|
||||||
MailboxSettingsManager provideMailboxSettingsManager(
|
MailboxSettingsManager provideMailboxSettingsManager(
|
||||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||||
return mailboxSettingsManager;
|
return mailboxSettingsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) {
|
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
|
||||||
return urlConverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
|
|
||||||
return mailboxApi;
|
return mailboxApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
MailboxUpdateValidator provideMailboxUpdateValidator(
|
MailboxPropertyValidator provideMailboxPropertyValidator(
|
||||||
ValidationManager validationManager,
|
ValidationManager validationManager, ClientHelper clientHelper,
|
||||||
ClientHelper clientHelper,
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
MetadataEncoder metadataEncoder,
|
MailboxPropertyValidator validator = new MailboxPropertyValidator(
|
||||||
Clock clock,
|
|
||||||
FeatureFlags featureFlags) {
|
|
||||||
MailboxUpdateValidator validator = new MailboxUpdateValidator(
|
|
||||||
clientHelper, metadataEncoder, clock);
|
clientHelper, metadataEncoder, clock);
|
||||||
if (featureFlags.shouldEnableMailbox()) {
|
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||||
validationManager.registerMessageValidator(CLIENT_ID,
|
validator);
|
||||||
MAJOR_VERSION, validator);
|
|
||||||
}
|
|
||||||
return validator;
|
return validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
List<MailboxVersion> provideClientSupports() {
|
|
||||||
return CLIENT_SUPPORTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
MailboxUpdateManager provideMailboxUpdateManager(
|
MailboxPropertyManager provideMailboxPropertyManager(
|
||||||
FeatureFlags featureFlags,
|
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
ValidationManager validationManager, ContactManager contactManager,
|
ValidationManager validationManager, ContactManager contactManager,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxUpdateManagerImpl mailboxUpdateManager) {
|
MailboxPropertyManagerImpl mailboxPropertyManager) {
|
||||||
if (featureFlags.shouldEnableMailbox()) {
|
lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager);
|
||||||
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
|
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
mailboxPropertyManager);
|
||||||
MAJOR_VERSION, mailboxUpdateManager);
|
contactManager.registerContactHook(mailboxPropertyManager);
|
||||||
contactManager.registerContactHook(mailboxUpdateManager);
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
MINOR_VERSION, mailboxPropertyManager);
|
||||||
MINOR_VERSION, mailboxUpdateManager);
|
mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager);
|
||||||
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
|
return mailboxPropertyManager;
|
||||||
}
|
|
||||||
return mailboxUpdateManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
|
|
||||||
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
|
|
||||||
if (featureFlags.shouldEnableMailbox()) {
|
|
||||||
eventBus.addListener(mailboxFileManager);
|
|
||||||
}
|
|
||||||
return mailboxFileManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
MailboxWorkerFactory provideMailboxWorkerFactory(
|
|
||||||
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
|
|
||||||
return mailboxWorkerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
MailboxClientFactory provideMailboxClientFactory(
|
|
||||||
MailboxClientFactoryImpl mailboxClientFactory) {
|
|
||||||
return mailboxClientFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
MailboxApiCaller provideMailboxApiCaller(
|
|
||||||
MailboxApiCallerImpl mailboxApiCaller) {
|
|
||||||
return mailboxApiCaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
TorReachabilityMonitor provideTorReachabilityMonitor(
|
|
||||||
TorReachabilityMonitorImpl reachabilityMonitor) {
|
|
||||||
return reachabilityMonitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
MailboxClientManager provideMailboxClientManager(
|
|
||||||
@EventExecutor Executor eventExecutor,
|
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
|
||||||
TransactionManager db,
|
|
||||||
ContactManager contactManager,
|
|
||||||
PluginManager pluginManager,
|
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
|
||||||
MailboxUpdateManager mailboxUpdateManager,
|
|
||||||
MailboxClientFactory mailboxClientFactory,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
FeatureFlags featureFlags,
|
|
||||||
LifecycleManager lifecycleManager,
|
|
||||||
EventBus eventBus) {
|
|
||||||
MailboxClientManager manager = new MailboxClientManager(eventExecutor,
|
|
||||||
dbExecutor, db, contactManager, pluginManager,
|
|
||||||
mailboxSettingsManager, mailboxUpdateManager,
|
|
||||||
mailboxClientFactory, reachabilityMonitor);
|
|
||||||
if (featureFlags.shouldEnableMailbox()) {
|
|
||||||
lifecycleManager.registerService(manager);
|
|
||||||
eventBus.addListener(manager);
|
|
||||||
}
|
|
||||||
return manager;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final MailboxApi api;
|
private final MailboxApi api;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
private final MailboxUpdateManager mailboxUpdateManager;
|
private final MailboxPropertyManager mailboxPropertyManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPairingTaskFactoryImpl(
|
MailboxPairingTaskFactoryImpl(
|
||||||
@@ -34,20 +34,20 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
MailboxApi api,
|
MailboxApi api,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxUpdateManager mailboxUpdateManager) {
|
MailboxPropertyManager mailboxPropertyManager) {
|
||||||
this.eventExecutor = eventExecutor;
|
this.eventExecutor = eventExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
||||||
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
||||||
crypto, clock, api, mailboxSettingsManager,
|
crypto, clock, api, mailboxSettingsManager,
|
||||||
mailboxUpdateManager);
|
mailboxPropertyManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
@@ -51,7 +51,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final MailboxApi api;
|
private final MailboxApi api;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
private final MailboxUpdateManager mailboxUpdateManager;
|
private final MailboxPropertyManager mailboxPropertyManager;
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
@@ -68,7 +68,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
MailboxApi api,
|
MailboxApi api,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxUpdateManager mailboxUpdateManager) {
|
MailboxPropertyManager mailboxPropertyManager) {
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.eventExecutor = eventExecutor;
|
this.eventExecutor = eventExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -76,7 +76,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||||
state = new MailboxPairingState.QrCodeReceived();
|
state = new MailboxPairingState.QrCodeReceived();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,15 +120,14 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
mailboxSettingsManager
|
mailboxSettingsManager
|
||||||
.setOwnMailboxProperties(txn, ownerProperties);
|
.setOwnMailboxProperties(txn, ownerProperties);
|
||||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time,
|
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
|
||||||
ownerProperties.getServerSupports());
|
|
||||||
// A (possibly new) mailbox is paired. Reset message retransmission
|
// A (possibly new) mailbox is paired. Reset message retransmission
|
||||||
// timers for contacts who doesn't have their own mailbox. This way,
|
// timers for contacts who doesn't have their own mailbox. This way,
|
||||||
// data stranded on our old mailbox will be re-uploaded to our new.
|
// data stranded on our old mailbox will be re-uploaded to our new.
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
MailboxUpdate update = mailboxUpdateManager.getRemoteUpdate(
|
MailboxPropertiesUpdate remoteProps = mailboxPropertyManager
|
||||||
txn, c.getId());
|
.getRemoteProperties(txn, c.getId());
|
||||||
if (update == null || !update.hasMailbox()) {
|
if (remoteProps == null) {
|
||||||
db.resetUnackedMessagesToSend(txn, c.getId());
|
db.resetUnackedMessagesToSend(txn, c.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,9 +177,11 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
LOG.info("QR code is valid");
|
LOG.info("QR code is valid");
|
||||||
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||||
String onion = crypto.encodeOnion(onionPubKey);
|
String onion = crypto.encodeOnion(onionPubKey);
|
||||||
|
String baseUrl = "http://" + onion + ".onion";
|
||||||
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
||||||
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
||||||
return new MailboxProperties(onion, setupToken, new ArrayList<>());
|
return new MailboxProperties(baseUrl, setupToken, true,
|
||||||
|
new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,303 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
||||||
|
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
||||||
|
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
||||||
|
IncomingMessageHook, MailboxHook {
|
||||||
|
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
|
private final MetadataParser metadataParser;
|
||||||
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
|
private final Clock clock;
|
||||||
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final Group localGroup;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
MetadataParser metadataParser,
|
||||||
|
ContactGroupFactory contactGroupFactory, Clock clock,
|
||||||
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
|
CryptoComponent crypto) {
|
||||||
|
this.db = db;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
|
this.metadataParser = metadataParser;
|
||||||
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
|
this.clock = clock;
|
||||||
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
|
this.crypto = crypto;
|
||||||
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||||
|
if (db.containsGroup(txn, localGroup.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
db.addGroup(txn, localGroup);
|
||||||
|
// Set things up for any pre-existing contacts
|
||||||
|
for (Contact c : db.getContacts(txn)) {
|
||||||
|
addingContact(txn, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
// Create a group to share with the contact
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.addGroup(txn, g);
|
||||||
|
// Apply the client's visibility to the contact group
|
||||||
|
Visibility client = clientVersioningManager
|
||||||
|
.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
|
// Attach the contact ID to the group
|
||||||
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
|
// If we are paired, create and send props to the newly added contact
|
||||||
|
MailboxProperties ownProps =
|
||||||
|
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||||
|
if (ownProps != null) {
|
||||||
|
createAndSendProperties(txn, c, ownProps.getOnion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
db.removeGroup(txn, getContactGroup(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mailboxPaired(Transaction txn, String ownOnion)
|
||||||
|
throws DbException {
|
||||||
|
for (Contact c : db.getContacts(txn)) {
|
||||||
|
createAndSendProperties(txn, c, ownOnion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||||
|
for (Contact c : db.getContacts(txn)) {
|
||||||
|
sendEmptyProperties(txn, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||||
|
Visibility v) throws DbException {
|
||||||
|
// Apply the client's visibility to the contact group
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||||
|
Metadata meta) throws DbException, InvalidMessageException {
|
||||||
|
try {
|
||||||
|
BdfDictionary d = metadataParser.parse(meta);
|
||||||
|
// Get latest non-local update in the same group (from same contact)
|
||||||
|
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
|
||||||
|
if (latest != null) {
|
||||||
|
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
|
||||||
|
db.deleteMessage(txn, latest.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.messageId);
|
||||||
|
} else {
|
||||||
|
// Delete this update, we already have a newer one
|
||||||
|
db.deleteMessage(txn, m.getId());
|
||||||
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||||
|
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
||||||
|
MailboxPropertiesUpdate p = parseProperties(body);
|
||||||
|
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
|
||||||
|
// Reset message retransmission timers for the contact. Avoiding
|
||||||
|
// messages getting stranded:
|
||||||
|
// - on our mailbox, if they now have a mailbox but didn't before
|
||||||
|
// - on the contact's old mailbox, if they removed their mailbox
|
||||||
|
// - on the contact's old mailbox, if they replaced their mailbox
|
||||||
|
db.resetUnackedMessagesToSend(txn, c);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
return getProperties(txn, db.getContact(txn, c), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
return getProperties(txn, db.getContact(txn, c), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and sends an update message to the given contact. The message
|
||||||
|
* holds our own mailbox's onion, and generated unique properties. All of
|
||||||
|
* which the contact needs to communicate with our Mailbox.
|
||||||
|
*/
|
||||||
|
private void createAndSendProperties(Transaction txn,
|
||||||
|
Contact c, String ownOnion) throws DbException {
|
||||||
|
MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
|
||||||
|
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||||
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||||
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
storeMessageReplaceLatest(txn, g.getId(), p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an empty update message to the given contact. The empty update
|
||||||
|
* indicates for the receiving contact that we no longer have a Mailbox that
|
||||||
|
* they can use.
|
||||||
|
*/
|
||||||
|
private void sendEmptyProperties(Transaction txn, Contact c)
|
||||||
|
throws DbException {
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
storeMessageReplaceLatest(txn, g.getId(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private MailboxPropertiesUpdate getProperties(Transaction txn,
|
||||||
|
Contact c, boolean local) throws DbException {
|
||||||
|
MailboxPropertiesUpdate p = null;
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
try {
|
||||||
|
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
||||||
|
if (latest != null) {
|
||||||
|
BdfList body =
|
||||||
|
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
|
p = parseProperties(body);
|
||||||
|
}
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
||||||
|
@Nullable MailboxPropertiesUpdate p) throws DbException {
|
||||||
|
try {
|
||||||
|
LatestUpdate latest = findLatest(txn, g, true);
|
||||||
|
long version = latest == null ? 1 : latest.version + 1;
|
||||||
|
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||||
|
encodeProperties(version, p));
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(MSG_KEY_VERSION, version);
|
||||||
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
|
if (latest != null) {
|
||||||
|
db.removeMessage(txn, latest.messageId);
|
||||||
|
}
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
|
// We should have at most 1 local and 1 remote
|
||||||
|
if (metadata.size() > 2) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
|
BdfDictionary meta = e.getValue();
|
||||||
|
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
|
||||||
|
return new LatestUpdate(e.getKey(),
|
||||||
|
meta.getLong(MSG_KEY_VERSION));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList encodeProperties(long version,
|
||||||
|
@Nullable MailboxPropertiesUpdate p) {
|
||||||
|
BdfDictionary dict = new BdfDictionary();
|
||||||
|
if (p != null) {
|
||||||
|
dict.put(PROP_KEY_ONION, p.getOnion());
|
||||||
|
dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
|
||||||
|
dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
|
||||||
|
dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
|
||||||
|
}
|
||||||
|
return BdfList.of(version, dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private MailboxPropertiesUpdate parseProperties(BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
BdfDictionary dict = body.getDictionary(1);
|
||||||
|
return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Group getContactGroup(Contact c) {
|
||||||
|
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
|
||||||
|
c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LatestUpdate {
|
||||||
|
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final long version;
|
||||||
|
|
||||||
|
private LatestUpdate(MessageId messageId, long version) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,15 +15,15 @@ import org.briarproject.bramble.api.system.Clock;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxUpdateValidator extends BdfMessageValidator {
|
class MailboxPropertyValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
MailboxUpdateValidator(ClientHelper clientHelper,
|
MailboxPropertyValidator(ClientHelper clientHelper,
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
super(clientHelper, metadataEncoder, clock);
|
super(clientHelper, metadataEncoder, clock);
|
||||||
}
|
}
|
||||||
@@ -31,19 +31,14 @@ class MailboxUpdateValidator extends BdfMessageValidator {
|
|||||||
@Override
|
@Override
|
||||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||||
BdfList body) throws InvalidMessageException, FormatException {
|
BdfList body) throws InvalidMessageException, FormatException {
|
||||||
// Version, Properties, clientSupports, serverSupports
|
// Version, properties
|
||||||
checkSize(body, 4);
|
checkSize(body, 2);
|
||||||
// Version
|
// Version
|
||||||
long version = body.getLong(0);
|
long version = body.getLong(0);
|
||||||
if (version < 0) throw new FormatException();
|
if (version < 0) throw new FormatException();
|
||||||
// clientSupports
|
|
||||||
BdfList clientSupports = body.getList(1);
|
|
||||||
// serverSupports
|
|
||||||
BdfList serverSupports = body.getList(2);
|
|
||||||
// Properties
|
// Properties
|
||||||
BdfDictionary dictionary = body.getDictionary(3);
|
BdfDictionary dictionary = body.getDictionary(1);
|
||||||
clientHelper.parseAndValidateMailboxUpdate(clientSupports,
|
clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary);
|
||||||
serverSupports, dictionary);
|
|
||||||
// Return the metadata
|
// Return the metadata
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_VERSION, version);
|
meta.put(MSG_KEY_VERSION, version);
|
||||||
@@ -9,8 +9,7 @@ import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxProblemEvent;
|
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
||||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
@@ -20,19 +19,17 @@ import java.util.List;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@ThreadSafe
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final String SETTINGS_NAMESPACE = "mailbox";
|
static final String SETTINGS_NAMESPACE = "mailbox";
|
||||||
// TODO: This currently stores the base URL, not the 56-char onion address
|
|
||||||
static final String SETTINGS_KEY_ONION = "onion";
|
static final String SETTINGS_KEY_ONION = "onion";
|
||||||
static final String SETTINGS_KEY_TOKEN = "token";
|
static final String SETTINGS_KEY_TOKEN = "token";
|
||||||
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
|
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
|
||||||
@@ -61,10 +58,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
String onion = s.get(SETTINGS_KEY_ONION);
|
String onion = s.get(SETTINGS_KEY_ONION);
|
||||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||||
List<MailboxVersion> serverSupports = parseServerSupports(s);
|
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||||
|
// We know we were paired, so we must have proper serverSupports
|
||||||
|
// TODO is throwing sensible? But it's done like that below on "parse error"
|
||||||
|
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||||
|
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||||
|
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
||||||
return new MailboxProperties(onion, tokenId, serverSupports);
|
return new MailboxProperties(onion, tokenId, true, serverSupports);
|
||||||
} catch (InvalidMailboxIdException e) {
|
} catch (InvalidMailboxIdException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -74,28 +80,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(SETTINGS_KEY_ONION, p.getOnion());
|
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||||
List<MailboxVersion> serverSupports = p.getServerSupports();
|
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||||
encodeServerSupports(serverSupports, s);
|
int[] ints = new int[serverSupports.size() * 2];
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
int i = 0;
|
||||||
for (MailboxHook hook : hooks) {
|
for (MailboxVersion v : serverSupports) {
|
||||||
hook.mailboxPaired(txn, p);
|
ints[i++] = v.getMajor();
|
||||||
|
ints[i++] = v.getMinor();
|
||||||
}
|
}
|
||||||
}
|
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeOwnMailboxProperties(Transaction txn) throws DbException {
|
|
||||||
Settings s = new Settings();
|
|
||||||
s.put(SETTINGS_KEY_ONION, "");
|
|
||||||
s.put(SETTINGS_KEY_TOKEN, "");
|
|
||||||
s.put(SETTINGS_KEY_ATTEMPTS, "");
|
|
||||||
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
|
|
||||||
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
|
|
||||||
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
|
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
for (MailboxHook hook : hooks) {
|
for (MailboxHook hook : hooks) {
|
||||||
hook.mailboxUnpaired(txn);
|
hook.mailboxPaired(txn, p.getOnion());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,60 +103,34 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
||||||
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
||||||
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
List<MailboxVersion> serverSupports;
|
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
|
||||||
try {
|
|
||||||
serverSupports = parseServerSupports(s);
|
|
||||||
} catch (DbException e) {
|
|
||||||
serverSupports = emptyList();
|
|
||||||
}
|
|
||||||
return new MailboxStatus(lastAttempt, lastSuccess, attempts,
|
|
||||||
serverSupports);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordSuccessfulConnection(Transaction txn, long now,
|
public void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
List<MailboxVersion> versions) throws DbException {
|
throws DbException {
|
||||||
// if we no longer have a paired mailbox, return
|
|
||||||
Settings oldSettings =
|
|
||||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
|
||||||
String onion = oldSettings.get(SETTINGS_KEY_ONION);
|
|
||||||
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
|
|
||||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
|
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
// record the successful connection
|
|
||||||
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
encodeServerSupports(versions, s);
|
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
for (MailboxHook hook : hooks) {
|
MailboxStatus status = new MailboxStatus(now, now, 0);
|
||||||
hook.serverSupportedVersionsReceived(txn, versions);
|
|
||||||
}
|
|
||||||
// broadcast status event
|
|
||||||
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
|
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
// if we no longer have a paired mailbox, return
|
|
||||||
Settings oldSettings =
|
Settings oldSettings =
|
||||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
String onion = oldSettings.get(SETTINGS_KEY_ONION);
|
|
||||||
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
|
|
||||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
|
|
||||||
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
||||||
Settings newSettings = new Settings();
|
Settings newSettings = new Settings();
|
||||||
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
||||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||||
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
|
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
||||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts,
|
|
||||||
serverSupports);
|
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -181,30 +152,4 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
if (isNullOrEmpty(filename)) return null;
|
if (isNullOrEmpty(filename)) return null;
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeServerSupports(List<MailboxVersion> serverSupports,
|
|
||||||
Settings s) {
|
|
||||||
int[] ints = new int[serverSupports.size() * 2];
|
|
||||||
int i = 0;
|
|
||||||
for (MailboxVersion v : serverSupports) {
|
|
||||||
ints[i++] = v.getMajor();
|
|
||||||
ints[i++] = v.getMinor();
|
|
||||||
}
|
|
||||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MailboxVersion> parseServerSupports(Settings s)
|
|
||||||
throws DbException {
|
|
||||||
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
|
|
||||||
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
|
||||||
// We know we were paired, so we must have proper serverSupports
|
|
||||||
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
List<MailboxVersion> serverSupports = new ArrayList<>();
|
|
||||||
for (int i = 0; i < ints.length - 1; i += 2) {
|
|
||||||
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
|
||||||
}
|
|
||||||
return serverSupports;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,487 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
|
|
||||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|
||||||
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
|
||||||
IncomingMessageHook, MailboxHook {
|
|
||||||
|
|
||||||
private final List<MailboxVersion> clientSupports;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final ClientHelper clientHelper;
|
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
|
||||||
private final MetadataParser metadataParser;
|
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
|
||||||
private final Clock clock;
|
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final Group localGroup;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports,
|
|
||||||
DatabaseComponent db, ClientHelper clientHelper,
|
|
||||||
ClientVersioningManager clientVersioningManager,
|
|
||||||
MetadataParser metadataParser,
|
|
||||||
ContactGroupFactory contactGroupFactory, Clock clock,
|
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
|
||||||
CryptoComponent crypto) {
|
|
||||||
this.clientSupports = clientSupports;
|
|
||||||
this.db = db;
|
|
||||||
this.clientHelper = clientHelper;
|
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
|
||||||
this.metadataParser = metadataParser;
|
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
|
||||||
this.clock = clock;
|
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
|
||||||
this.crypto = crypto;
|
|
||||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
|
||||||
MAJOR_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
|
||||||
if (db.containsGroup(txn, localGroup.getId())) {
|
|
||||||
try {
|
|
||||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
|
|
||||||
txn, localGroup.getId());
|
|
||||||
BdfList sent = meta.getList(GROUP_KEY_SENT_CLIENT_SUPPORTS);
|
|
||||||
if (clientHelper.parseMailboxVersionList(sent)
|
|
||||||
.equals(clientSupports)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
// Our current clientSupports list has changed compared to what we
|
|
||||||
// last sent out.
|
|
||||||
for (Contact c : db.getContacts(txn)) {
|
|
||||||
MailboxUpdate latest = getLocalUpdate(txn, c.getId());
|
|
||||||
MailboxUpdate updated;
|
|
||||||
if (latest.hasMailbox()) {
|
|
||||||
updated = new MailboxUpdateWithMailbox(
|
|
||||||
(MailboxUpdateWithMailbox) latest, clientSupports);
|
|
||||||
} else {
|
|
||||||
updated = new MailboxUpdate(clientSupports);
|
|
||||||
}
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
storeMessageReplaceLatest(txn, g.getId(), updated);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
db.addGroup(txn, localGroup);
|
|
||||||
// Set things up for any pre-existing contacts
|
|
||||||
for (Contact c : db.getContacts(txn)) {
|
|
||||||
addingContact(txn, c, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
|
||||||
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
|
||||||
encodeSupportsList(clientSupports)));
|
|
||||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
|
||||||
addingContact(txn, c, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param attachEvent True if a {@link MailboxUpdateSentToNewContactEvent}
|
|
||||||
* should be attached to the transaction. We should only do this when
|
|
||||||
* adding a new contact, not when setting up this client for an existing
|
|
||||||
* contact.
|
|
||||||
*/
|
|
||||||
private void addingContact(Transaction txn, Contact c, boolean attachEvent)
|
|
||||||
throws DbException {
|
|
||||||
// Create a group to share with the contact
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
db.addGroup(txn, g);
|
|
||||||
// Apply the client's visibility to the contact group
|
|
||||||
Visibility client = clientVersioningManager
|
|
||||||
.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
|
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
|
||||||
// Attach the contact ID to the group
|
|
||||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
|
||||||
MailboxProperties ownProps =
|
|
||||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
|
||||||
MailboxUpdate u;
|
|
||||||
if (ownProps != null) {
|
|
||||||
// We are paired, create and send props to the newly added contact
|
|
||||||
u = createAndSendUpdateWithMailbox(txn, c,
|
|
||||||
ownProps.getServerSupports(), ownProps.getOnion());
|
|
||||||
} else {
|
|
||||||
// Not paired, but we still want to get our clientSupports sent
|
|
||||||
u = sendUpdateNoMailbox(txn, c);
|
|
||||||
}
|
|
||||||
if (attachEvent) {
|
|
||||||
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
|
||||||
db.removeGroup(txn, getContactGroup(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mailboxPaired(Transaction txn, MailboxProperties p)
|
|
||||||
throws DbException {
|
|
||||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
|
|
||||||
for (Contact c : db.getContacts(txn)) {
|
|
||||||
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c,
|
|
||||||
p.getServerSupports(), p.getOnion());
|
|
||||||
localUpdates.put(c.getId(), u);
|
|
||||||
}
|
|
||||||
txn.attach(new MailboxPairedEvent(p, localUpdates));
|
|
||||||
// Store the list of server-supported versions
|
|
||||||
try {
|
|
||||||
storeSentServerSupports(txn, p.getServerSupports());
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
|
||||||
Map<ContactId, MailboxUpdate> localUpdates = new HashMap<>();
|
|
||||||
for (Contact c : db.getContacts(txn)) {
|
|
||||||
MailboxUpdate u = sendUpdateNoMailbox(txn, c);
|
|
||||||
localUpdates.put(c.getId(), u);
|
|
||||||
}
|
|
||||||
txn.attach(new MailboxUnpairedEvent(localUpdates));
|
|
||||||
// Remove the list of server-supported versions
|
|
||||||
try {
|
|
||||||
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
|
||||||
GROUP_KEY_SENT_SERVER_SUPPORTS, NULL_VALUE));
|
|
||||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serverSupportedVersionsReceived(Transaction txn,
|
|
||||||
List<MailboxVersion> serverSupports) throws DbException {
|
|
||||||
try {
|
|
||||||
List<MailboxVersion> oldServerSupports =
|
|
||||||
loadSentServerSupports(txn);
|
|
||||||
if (serverSupports.equals(oldServerSupports)) return;
|
|
||||||
storeSentServerSupports(txn, serverSupports);
|
|
||||||
for (Contact c : db.getContacts(txn)) {
|
|
||||||
Group contactGroup = getContactGroup(c);
|
|
||||||
LatestUpdate latest =
|
|
||||||
findLatest(txn, contactGroup.getId(), true);
|
|
||||||
// This method should only be called when we have a mailbox,
|
|
||||||
// in which case we should have sent a local update to every
|
|
||||||
// contact
|
|
||||||
if (latest == null) throw new DbException();
|
|
||||||
BdfList body =
|
|
||||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
|
||||||
MailboxUpdate oldUpdate = parseUpdate(body);
|
|
||||||
if (!oldUpdate.hasMailbox()) throw new DbException();
|
|
||||||
MailboxUpdateWithMailbox newUpdate = updateServerSupports(
|
|
||||||
(MailboxUpdateWithMailbox) oldUpdate, serverSupports);
|
|
||||||
storeMessageReplaceLatest(txn, contactGroup.getId(), newUpdate,
|
|
||||||
latest);
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeSentServerSupports(Transaction txn,
|
|
||||||
List<MailboxVersion> serverSupports)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
|
||||||
GROUP_KEY_SENT_SERVER_SUPPORTS,
|
|
||||||
encodeSupportsList(serverSupports)));
|
|
||||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MailboxVersion> loadSentServerSupports(Transaction txn)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
|
||||||
localGroup.getId());
|
|
||||||
BdfList serverSupports =
|
|
||||||
meta.getOptionalList(GROUP_KEY_SENT_SERVER_SUPPORTS);
|
|
||||||
if (serverSupports == null) return emptyList();
|
|
||||||
return clientHelper.parseMailboxVersionList(serverSupports);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link MailboxUpdateWithMailbox} that updates the list
|
|
||||||
* of server-supported API versions in the given
|
|
||||||
* {@link MailboxUpdateWithMailbox}.
|
|
||||||
*/
|
|
||||||
private MailboxUpdateWithMailbox updateServerSupports(
|
|
||||||
MailboxUpdateWithMailbox old, List<MailboxVersion> serverSupports) {
|
|
||||||
MailboxProperties oldProps = old.getMailboxProperties();
|
|
||||||
MailboxProperties newProps = new MailboxProperties(oldProps.getOnion(),
|
|
||||||
oldProps.getAuthToken(), serverSupports,
|
|
||||||
requireNonNull(oldProps.getInboxId()),
|
|
||||||
requireNonNull(oldProps.getOutboxId()));
|
|
||||||
return new MailboxUpdateWithMailbox(old.getClientSupports(), newProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
|
||||||
Visibility v) throws DbException {
|
|
||||||
// Apply the client's visibility to the contact group
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
|
||||||
Metadata meta) throws DbException, InvalidMessageException {
|
|
||||||
try {
|
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
|
||||||
// Get latest non-local update in the same group (from same contact)
|
|
||||||
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
|
|
||||||
if (latest != null) {
|
|
||||||
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
|
|
||||||
db.deleteMessage(txn, latest.messageId);
|
|
||||||
db.deleteMessageMetadata(txn, latest.messageId);
|
|
||||||
} else {
|
|
||||||
// Delete this update, we already have a newer one
|
|
||||||
db.deleteMessage(txn, m.getId());
|
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
|
||||||
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
|
||||||
MailboxUpdate u = parseUpdate(body);
|
|
||||||
txn.attach(new RemoteMailboxUpdateEvent(c, u));
|
|
||||||
// Reset message retransmission timers for the contact. Avoiding
|
|
||||||
// messages getting stranded:
|
|
||||||
// - on our mailbox, if they now have a mailbox but didn't before
|
|
||||||
// - on the contact's old mailbox, if they removed their mailbox
|
|
||||||
// - on the contact's old mailbox, if they replaced their mailbox
|
|
||||||
db.resetUnackedMessagesToSend(txn, c);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
}
|
|
||||||
return ACCEPT_DO_NOT_SHARE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
|
||||||
throws DbException {
|
|
||||||
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true);
|
|
||||||
// An update (with or without mailbox) is created when contact is added
|
|
||||||
if (local == null) {
|
|
||||||
throw new DbException();
|
|
||||||
}
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
|
||||||
throws DbException {
|
|
||||||
return getUpdate(txn, db.getContact(txn, c), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and sends an update message to the given contact. The message
|
|
||||||
* holds our own mailbox's onion, generated unique properties, and lists of
|
|
||||||
* supported Mailbox API version(s). All of which the contact needs to
|
|
||||||
* communicate with our Mailbox.
|
|
||||||
*/
|
|
||||||
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox(
|
|
||||||
Transaction txn, Contact c, List<MailboxVersion> serverSupports,
|
|
||||||
String ownOnion) throws DbException {
|
|
||||||
MailboxProperties properties = new MailboxProperties(ownOnion,
|
|
||||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
|
||||||
serverSupports,
|
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
|
||||||
MailboxUpdateWithMailbox u =
|
|
||||||
new MailboxUpdateWithMailbox(clientSupports, properties);
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an update message with empty properties to the given contact. The
|
|
||||||
* empty update indicates for the receiving contact that we don't have any
|
|
||||||
* Mailbox that they can use. It still includes the list of Mailbox API
|
|
||||||
* version(s) that we support as a client.
|
|
||||||
*/
|
|
||||||
private MailboxUpdate sendUpdateNoMailbox(Transaction txn, Contact c)
|
|
||||||
throws DbException {
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
MailboxUpdate u = new MailboxUpdate(clientSupports);
|
|
||||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local)
|
|
||||||
throws DbException {
|
|
||||||
MailboxUpdate u = null;
|
|
||||||
Group g = getContactGroup(c);
|
|
||||||
try {
|
|
||||||
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
|
||||||
if (latest != null) {
|
|
||||||
BdfList body =
|
|
||||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
|
||||||
u = parseUpdate(body);
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
|
||||||
MailboxUpdate u) throws DbException {
|
|
||||||
try {
|
|
||||||
LatestUpdate latest = findLatest(txn, g, true);
|
|
||||||
storeMessageReplaceLatest(txn, g, u, latest);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
|
||||||
MailboxUpdate u, @Nullable LatestUpdate latest)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
long version = latest == null ? 1 : latest.version + 1;
|
|
||||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
|
||||||
encodeProperties(version, u));
|
|
||||||
BdfDictionary meta = new BdfDictionary();
|
|
||||||
meta.put(MSG_KEY_VERSION, version);
|
|
||||||
meta.put(MSG_KEY_LOCAL, true);
|
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
|
||||||
if (latest != null) {
|
|
||||||
db.removeMessage(txn, latest.messageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
|
||||||
throws DbException, FormatException {
|
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
|
||||||
// We should have at most 1 local and 1 remote
|
|
||||||
if (metadata.size() > 2) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
|
||||||
BdfDictionary meta = e.getValue();
|
|
||||||
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
|
|
||||||
return new LatestUpdate(e.getKey(),
|
|
||||||
meta.getLong(MSG_KEY_VERSION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BdfList encodeProperties(long version, MailboxUpdate u) {
|
|
||||||
BdfDictionary dict = new BdfDictionary();
|
|
||||||
BdfList serverSupports = new BdfList();
|
|
||||||
if (u.hasMailbox()) {
|
|
||||||
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u;
|
|
||||||
MailboxProperties properties = um.getMailboxProperties();
|
|
||||||
serverSupports = encodeSupportsList(properties.getServerSupports());
|
|
||||||
dict.put(PROP_KEY_ONION, properties.getOnion());
|
|
||||||
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
|
|
||||||
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
|
|
||||||
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
|
|
||||||
}
|
|
||||||
return BdfList.of(version, encodeSupportsList(u.getClientSupports()),
|
|
||||||
serverSupports, dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) {
|
|
||||||
BdfList supports = new BdfList();
|
|
||||||
for (MailboxVersion version : supportsList) {
|
|
||||||
supports.add(BdfList.of(version.getMajor(), version.getMinor()));
|
|
||||||
}
|
|
||||||
return supports;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MailboxUpdate parseUpdate(BdfList body) throws FormatException {
|
|
||||||
BdfList clientSupports = body.getList(1);
|
|
||||||
BdfList serverSupports = body.getList(2);
|
|
||||||
BdfDictionary dict = body.getDictionary(3);
|
|
||||||
return clientHelper.parseAndValidateMailboxUpdate(clientSupports,
|
|
||||||
serverSupports, dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
|
|
||||||
c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LatestUpdate {
|
|
||||||
|
|
||||||
private final MessageId messageId;
|
|
||||||
private final long version;
|
|
||||||
|
|
||||||
private LatestUpdate(MessageId messageId, long version) {
|
|
||||||
this.messageId = messageId;
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,464 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
|
||||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
|
||||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
|
||||||
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
|
||||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
|
||||||
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.mailbox.MailboxConstants.MAX_LATENCY;
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.delete;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
|
|
||||||
EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the worker is started it checks for data to send. If data is ready
|
|
||||||
* to send, the worker waits for a connectivity check, then writes and
|
|
||||||
* uploads a file and checks again for data to send.
|
|
||||||
* <p>
|
|
||||||
* If data is due to be sent at some time in the future, the worker
|
|
||||||
* schedules a wakeup for that time and also listens for events indicating
|
|
||||||
* that new data may be ready to send.
|
|
||||||
* <p>
|
|
||||||
* If there's no data to send, the worker listens for events indicating
|
|
||||||
* that new data may be ready to send.
|
|
||||||
* <p>
|
|
||||||
* Whenever we're directly connected to the contact, the worker doesn't
|
|
||||||
* check for data to send or start connectivity checks until the contact
|
|
||||||
* disconnects. However, if the worker has already started writing and
|
|
||||||
* uploading a file when the contact connects, the worker will finish the
|
|
||||||
* upload.
|
|
||||||
*/
|
|
||||||
private enum State {
|
|
||||||
CREATED,
|
|
||||||
CONNECTED_TO_CONTACT,
|
|
||||||
CHECKING_FOR_DATA,
|
|
||||||
WAITING_FOR_DATA,
|
|
||||||
CONNECTIVITY_CHECK,
|
|
||||||
WRITING_UPLOADING,
|
|
||||||
DESTROYED
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(MailboxUploadWorker.class.getName());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When we're waiting for data to send and an event indicates that new data
|
|
||||||
* may have become available, wait this long before checking the DB. This
|
|
||||||
* should help to avoid creating lots of small files when several acks or
|
|
||||||
* messages become available to send in a short period (eg when reading a
|
|
||||||
* file downloaded from a mailbox).
|
|
||||||
* <p>
|
|
||||||
* Package access for testing.
|
|
||||||
*/
|
|
||||||
static final long CHECK_DELAY_MS = 5_000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long to wait before retrying when an exception occurs while writing
|
|
||||||
* a file.
|
|
||||||
* <p>
|
|
||||||
* Package access for testing.
|
|
||||||
*/
|
|
||||||
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final Clock clock;
|
|
||||||
private final TaskScheduler taskScheduler;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final ConnectionRegistry connectionRegistry;
|
|
||||||
private final ConnectivityChecker connectivityChecker;
|
|
||||||
private final MailboxApiCaller mailboxApiCaller;
|
|
||||||
private final MailboxApi mailboxApi;
|
|
||||||
private final MailboxFileManager mailboxFileManager;
|
|
||||||
private final MailboxProperties mailboxProperties;
|
|
||||||
private final MailboxFolderId folderId;
|
|
||||||
private final ContactId contactId;
|
|
||||||
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private State state = State.CREATED;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private File file = null;
|
|
||||||
|
|
||||||
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
|
|
||||||
DatabaseComponent db,
|
|
||||||
Clock clock,
|
|
||||||
TaskScheduler taskScheduler,
|
|
||||||
EventBus eventBus,
|
|
||||||
ConnectionRegistry connectionRegistry,
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxFileManager mailboxFileManager,
|
|
||||||
MailboxProperties mailboxProperties,
|
|
||||||
MailboxFolderId folderId,
|
|
||||||
ContactId contactId) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.db = db;
|
|
||||||
this.clock = clock;
|
|
||||||
this.taskScheduler = taskScheduler;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.connectionRegistry = connectionRegistry;
|
|
||||||
this.connectivityChecker = connectivityChecker;
|
|
||||||
this.mailboxApiCaller = mailboxApiCaller;
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
this.mailboxFileManager = mailboxFileManager;
|
|
||||||
this.mailboxProperties = mailboxProperties;
|
|
||||||
this.folderId = folderId;
|
|
||||||
this.contactId = contactId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
LOG.info("Started");
|
|
||||||
synchronized (lock) {
|
|
||||||
// Don't allow the worker to be reused
|
|
||||||
if (state != State.CREATED) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
}
|
|
||||||
ioExecutor.execute(this::checkForDataToSend);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
LOG.info("Destroyed");
|
|
||||||
Cancellable wakeupTask, checkTask, apiCall;
|
|
||||||
File file;
|
|
||||||
synchronized (lock) {
|
|
||||||
state = State.DESTROYED;
|
|
||||||
wakeupTask = this.wakeupTask;
|
|
||||||
this.wakeupTask = null;
|
|
||||||
checkTask = this.checkTask;
|
|
||||||
this.checkTask = null;
|
|
||||||
apiCall = this.apiCall;
|
|
||||||
this.apiCall = null;
|
|
||||||
file = this.file;
|
|
||||||
this.file = null;
|
|
||||||
}
|
|
||||||
if (wakeupTask != null) wakeupTask.cancel();
|
|
||||||
if (checkTask != null) checkTask.cancel();
|
|
||||||
if (apiCall != null) apiCall.cancel();
|
|
||||||
if (file != null) delete(file);
|
|
||||||
connectivityChecker.removeObserver(this);
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void checkForDataToSend() {
|
|
||||||
synchronized (lock) {
|
|
||||||
checkTask = null;
|
|
||||||
if (state != State.CHECKING_FOR_DATA) return;
|
|
||||||
// Check whether we're directly connected to the contact. Calling
|
|
||||||
// this while holding the lock isn't ideal, but it avoids races
|
|
||||||
if (connectionRegistry.isConnected(contactId)) {
|
|
||||||
state = State.CONNECTED_TO_CONTACT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.info("Checking for data to send");
|
|
||||||
try {
|
|
||||||
db.transaction(true, txn -> {
|
|
||||||
long nextSendTime;
|
|
||||||
if (db.containsAcksToSend(txn, contactId)) {
|
|
||||||
nextSendTime = 0L;
|
|
||||||
} else {
|
|
||||||
nextSendTime = db.getNextSendTime(txn, contactId,
|
|
||||||
MAX_LATENCY);
|
|
||||||
}
|
|
||||||
// Handle the result on the event executor to avoid races with
|
|
||||||
// incoming events
|
|
||||||
txn.attach(() -> handleNextSendTime(nextSendTime));
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void handleNextSendTime(long nextSendTime) {
|
|
||||||
if (nextSendTime == Long.MAX_VALUE) {
|
|
||||||
// Nothing is sendable now or due to be sent in the future. Wait
|
|
||||||
// for an event indicating that new data may be ready to send
|
|
||||||
waitForDataToSend();
|
|
||||||
} else {
|
|
||||||
// Work out the delay until data's ready to send (may be negative)
|
|
||||||
long delay = nextSendTime - clock.currentTimeMillis();
|
|
||||||
if (delay > 0) {
|
|
||||||
// Schedule a wakeup when data will be ready to send. If an
|
|
||||||
// event is received in the meantime indicating that new data
|
|
||||||
// may be ready to send, we'll cancel the wakeup
|
|
||||||
scheduleWakeup(delay);
|
|
||||||
} else {
|
|
||||||
// Data is ready to send now
|
|
||||||
checkConnectivity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void waitForDataToSend() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CHECKING_FOR_DATA) return;
|
|
||||||
state = State.WAITING_FOR_DATA;
|
|
||||||
LOG.info("Waiting for data to send");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void scheduleWakeup(long delay) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CHECKING_FOR_DATA) return;
|
|
||||||
state = State.WAITING_FOR_DATA;
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Scheduling wakeup in " + delay + " ms");
|
|
||||||
}
|
|
||||||
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
|
|
||||||
delay, MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void wakeUp() {
|
|
||||||
LOG.info("Woke up");
|
|
||||||
synchronized (lock) {
|
|
||||||
wakeupTask = null;
|
|
||||||
if (state != State.WAITING_FOR_DATA) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
}
|
|
||||||
checkForDataToSend();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void checkConnectivity() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CHECKING_FOR_DATA) return;
|
|
||||||
state = State.CONNECTIVITY_CHECK;
|
|
||||||
}
|
|
||||||
LOG.info("Checking connectivity");
|
|
||||||
// Avoid leaking observer in case destroy() is called concurrently
|
|
||||||
// before observer is added
|
|
||||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
|
||||||
boolean destroyed;
|
|
||||||
synchronized (lock) {
|
|
||||||
destroyed = state == State.DESTROYED;
|
|
||||||
}
|
|
||||||
if (destroyed) connectivityChecker.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectivityCheckSucceeded() {
|
|
||||||
LOG.info("Connectivity check succeeded");
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CONNECTIVITY_CHECK) return;
|
|
||||||
state = State.WRITING_UPLOADING;
|
|
||||||
}
|
|
||||||
ioExecutor.execute(this::writeAndUploadFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void writeAndUploadFile() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WRITING_UPLOADING) return;
|
|
||||||
}
|
|
||||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
|
||||||
File file;
|
|
||||||
try {
|
|
||||||
file = mailboxFileManager.createAndWriteTempFileForUpload(
|
|
||||||
contactId, sessionRecord);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
// Try again after a delay
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WRITING_UPLOADING) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
|
||||||
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean deleteFile = false;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.WRITING_UPLOADING) {
|
|
||||||
this.file = file;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
new SimpleApiCall(() -> apiCallUploadFile(file,
|
|
||||||
sessionRecord)));
|
|
||||||
} else {
|
|
||||||
deleteFile = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (deleteFile) delete(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void apiCallUploadFile(File file,
|
|
||||||
OutgoingSessionRecord sessionRecord)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WRITING_UPLOADING) return;
|
|
||||||
}
|
|
||||||
LOG.info("Uploading file");
|
|
||||||
mailboxApi.addFile(mailboxProperties, folderId, file);
|
|
||||||
markMessagesSentOrAcked(sessionRecord);
|
|
||||||
delete(file);
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WRITING_UPLOADING) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
apiCall = null;
|
|
||||||
this.file = null;
|
|
||||||
}
|
|
||||||
checkForDataToSend();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
|
|
||||||
Collection<MessageId> acked = sessionRecord.getAckedIds();
|
|
||||||
Collection<MessageId> sent = sessionRecord.getSentIds();
|
|
||||||
try {
|
|
||||||
db.transaction(false, txn -> {
|
|
||||||
if (!acked.isEmpty()) {
|
|
||||||
db.setAckSent(txn, contactId, acked);
|
|
||||||
}
|
|
||||||
if (!sent.isEmpty()) {
|
|
||||||
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof MessageToAckEvent) {
|
|
||||||
MessageToAckEvent m = (MessageToAckEvent) e;
|
|
||||||
if (m.getContactId().equals(contactId)) {
|
|
||||||
LOG.info("Message to ack");
|
|
||||||
onDataToSend();
|
|
||||||
}
|
|
||||||
} else if (e instanceof MessageSharedEvent) {
|
|
||||||
MessageSharedEvent m = (MessageSharedEvent) e;
|
|
||||||
// If the contact is present in the map (ie the value is not null)
|
|
||||||
// and the value is true, the message's group is shared with the
|
|
||||||
// contact and therefore the message may now be sendable
|
|
||||||
if (m.getGroupVisibility().get(contactId) == TRUE) {
|
|
||||||
LOG.info("Message shared");
|
|
||||||
onDataToSend();
|
|
||||||
}
|
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
|
||||||
if (g.getVisibility() == SHARED &&
|
|
||||||
g.getAffectedContacts().contains(contactId)) {
|
|
||||||
LOG.info("Group shared");
|
|
||||||
onDataToSend();
|
|
||||||
}
|
|
||||||
} else if (e instanceof ContactConnectedEvent) {
|
|
||||||
ContactConnectedEvent c = (ContactConnectedEvent) e;
|
|
||||||
if (c.getContactId().equals(contactId)) {
|
|
||||||
LOG.info("Contact connected");
|
|
||||||
onContactConnected();
|
|
||||||
}
|
|
||||||
} else if (e instanceof ContactDisconnectedEvent) {
|
|
||||||
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
|
|
||||||
if (c.getContactId().equals(contactId)) {
|
|
||||||
LOG.info("Contact disconnected");
|
|
||||||
onContactDisconnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onDataToSend() {
|
|
||||||
Cancellable wakeupTask;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.WAITING_FOR_DATA) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
wakeupTask = this.wakeupTask;
|
|
||||||
this.wakeupTask = null;
|
|
||||||
// Delay the check to avoid creating lots of small files
|
|
||||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
|
||||||
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
|
|
||||||
}
|
|
||||||
// If we had scheduled a wakeup when data was due to be sent, cancel it
|
|
||||||
if (wakeupTask != null) wakeupTask.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactConnected() {
|
|
||||||
Cancellable wakeupTask = null, checkTask = null;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
// If we're checking for data to send, waiting for data to send,
|
|
||||||
// or checking connectivity then wait until we disconnect from
|
|
||||||
// the contact before proceeding. If we're writing or uploading
|
|
||||||
// a file then continue
|
|
||||||
if (state == State.CHECKING_FOR_DATA ||
|
|
||||||
state == State.WAITING_FOR_DATA ||
|
|
||||||
state == State.CONNECTIVITY_CHECK) {
|
|
||||||
state = State.CONNECTED_TO_CONTACT;
|
|
||||||
wakeupTask = this.wakeupTask;
|
|
||||||
this.wakeupTask = null;
|
|
||||||
checkTask = this.checkTask;
|
|
||||||
this.checkTask = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wakeupTask != null) wakeupTask.cancel();
|
|
||||||
if (checkTask != null) checkTask.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactDisconnected() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CONNECTED_TO_CONTACT) return;
|
|
||||||
state = State.CHECKING_FOR_DATA;
|
|
||||||
}
|
|
||||||
ioExecutor.execute(this::checkForDataToSend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A worker that downloads files from a contact's mailbox.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxWorker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously starts the worker.
|
|
||||||
*/
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the worker and cancels any pending tasks or retries.
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
interface MailboxWorkerFactory {
|
|
||||||
|
|
||||||
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId,
|
|
||||||
ContactId contactId);
|
|
||||||
|
|
||||||
MailboxWorker createDownloadWorkerForContactMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties);
|
|
||||||
|
|
||||||
MailboxWorker createDownloadWorkerForOwnMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties);
|
|
||||||
|
|
||||||
MailboxWorker createContactListWorkerForOwnMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxProperties properties);
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final Clock clock;
|
|
||||||
private final TaskScheduler taskScheduler;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final ConnectionRegistry connectionRegistry;
|
|
||||||
private final MailboxApiCaller mailboxApiCaller;
|
|
||||||
private final MailboxApi mailboxApi;
|
|
||||||
private final MailboxFileManager mailboxFileManager;
|
|
||||||
private final MailboxUpdateManager mailboxUpdateManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
|
|
||||||
DatabaseComponent db,
|
|
||||||
Clock clock,
|
|
||||||
TaskScheduler taskScheduler,
|
|
||||||
EventBus eventBus,
|
|
||||||
ConnectionRegistry connectionRegistry,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxFileManager mailboxFileManager,
|
|
||||||
MailboxUpdateManager mailboxUpdateManager) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.db = db;
|
|
||||||
this.clock = clock;
|
|
||||||
this.taskScheduler = taskScheduler;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.connectionRegistry = connectionRegistry;
|
|
||||||
this.mailboxApiCaller = mailboxApiCaller;
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
this.mailboxFileManager = mailboxFileManager;
|
|
||||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxWorker createUploadWorker(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId,
|
|
||||||
ContactId contactId) {
|
|
||||||
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
|
|
||||||
clock, taskScheduler, eventBus, connectionRegistry,
|
|
||||||
connectivityChecker, mailboxApiCaller, mailboxApi,
|
|
||||||
mailboxFileManager, properties, folderId, contactId);
|
|
||||||
eventBus.addListener(worker);
|
|
||||||
return worker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxWorker createDownloadWorkerForContactMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties) {
|
|
||||||
return new ContactMailboxDownloadWorker(connectivityChecker,
|
|
||||||
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
|
||||||
mailboxFileManager, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxWorker createDownloadWorkerForOwnMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
MailboxProperties properties) {
|
|
||||||
return new OwnMailboxDownloadWorker(connectivityChecker,
|
|
||||||
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
|
||||||
mailboxFileManager, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MailboxWorker createContactListWorkerForOwnMailbox(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxProperties properties) {
|
|
||||||
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
|
|
||||||
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
|
|
||||||
mailboxApi, mailboxUpdateManager, properties);
|
|
||||||
eventBus.addListener(worker);
|
|
||||||
return worker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
|
||||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.HOURS;
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class OwnMailboxClient implements MailboxClient, ConnectivityObserver {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How often to check our own mailbox's connectivity.
|
|
||||||
* <p>
|
|
||||||
* Package access for testing.
|
|
||||||
*/
|
|
||||||
static final long CONNECTIVITY_CHECK_INTERVAL_MS = HOURS.toMillis(1);
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(OwnMailboxClient.class.getName());
|
|
||||||
|
|
||||||
private final MailboxWorkerFactory workerFactory;
|
|
||||||
private final ConnectivityChecker connectivityChecker;
|
|
||||||
private final TorReachabilityMonitor reachabilityMonitor;
|
|
||||||
private final TaskScheduler taskScheduler;
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final MailboxProperties properties;
|
|
||||||
private final MailboxWorker contactListWorker;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload workers: one worker per contact assigned for upload.
|
|
||||||
*/
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private final Map<ContactId, MailboxWorker> uploadWorkers = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download worker: shared between all contacts assigned for download.
|
|
||||||
* Null if no contacts are assigned for download.
|
|
||||||
*/
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private MailboxWorker downloadWorker = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IDs of contacts assigned for download, so that we know when to
|
|
||||||
* create/destroy the download worker.
|
|
||||||
*/
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private final Set<ContactId> assignedForDownload = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scheduled task for periodically checking whether the mailbox is
|
|
||||||
* reachable.
|
|
||||||
*/
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private Cancellable connectivityTask = null;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private boolean destroyed = false;
|
|
||||||
|
|
||||||
OwnMailboxClient(MailboxWorkerFactory workerFactory,
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
|
||||||
TaskScheduler taskScheduler,
|
|
||||||
@IoExecutor Executor ioExecutor,
|
|
||||||
MailboxProperties properties) {
|
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
this.workerFactory = workerFactory;
|
|
||||||
this.connectivityChecker = connectivityChecker;
|
|
||||||
this.reachabilityMonitor = reachabilityMonitor;
|
|
||||||
this.taskScheduler = taskScheduler;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.properties = properties;
|
|
||||||
contactListWorker = workerFactory.createContactListWorkerForOwnMailbox(
|
|
||||||
connectivityChecker, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
LOG.info("Started");
|
|
||||||
checkConnectivity();
|
|
||||||
contactListWorker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
LOG.info("Destroyed");
|
|
||||||
List<MailboxWorker> uploadWorkers;
|
|
||||||
MailboxWorker downloadWorker;
|
|
||||||
Cancellable connectivityTask;
|
|
||||||
synchronized (lock) {
|
|
||||||
uploadWorkers = new ArrayList<>(this.uploadWorkers.values());
|
|
||||||
this.uploadWorkers.clear();
|
|
||||||
downloadWorker = this.downloadWorker;
|
|
||||||
this.downloadWorker = null;
|
|
||||||
connectivityTask = this.connectivityTask;
|
|
||||||
this.connectivityTask = null;
|
|
||||||
destroyed = true;
|
|
||||||
}
|
|
||||||
// Destroy the workers (with apologies to Mr Marx and Mr Engels)
|
|
||||||
for (MailboxWorker worker : uploadWorkers) worker.destroy();
|
|
||||||
if (downloadWorker != null) downloadWorker.destroy();
|
|
||||||
// If a connectivity check is scheduled, cancel it
|
|
||||||
if (connectivityTask != null) connectivityTask.cancel();
|
|
||||||
contactListWorker.destroy();
|
|
||||||
// The connectivity checker belongs to the client, so it should be
|
|
||||||
// destroyed. The Tor reachability monitor is shared between clients,
|
|
||||||
// so it should not be destroyed
|
|
||||||
connectivityChecker.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assignContactForUpload(ContactId contactId,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId) {
|
|
||||||
LOG.info("Contact assigned for upload");
|
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
|
||||||
connectivityChecker, properties, folderId, contactId);
|
|
||||||
synchronized (lock) {
|
|
||||||
MailboxWorker old = uploadWorkers.put(contactId, uploadWorker);
|
|
||||||
if (old != null) throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
uploadWorker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deassignContactForUpload(ContactId contactId) {
|
|
||||||
LOG.info("Contact deassigned for upload");
|
|
||||||
MailboxWorker uploadWorker;
|
|
||||||
synchronized (lock) {
|
|
||||||
uploadWorker = uploadWorkers.remove(contactId);
|
|
||||||
}
|
|
||||||
if (uploadWorker != null) uploadWorker.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assignContactForDownload(ContactId contactId,
|
|
||||||
MailboxProperties properties, MailboxFolderId folderId) {
|
|
||||||
LOG.info("Contact assigned for download");
|
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
// Create a download worker if we don't already have one. The worker
|
|
||||||
// will use the API to discover which folders have files to download,
|
|
||||||
// so it doesn't need to track the set of assigned contacts
|
|
||||||
MailboxWorker toStart = null;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (!assignedForDownload.add(contactId)) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
if (downloadWorker == null) {
|
|
||||||
toStart = workerFactory.createDownloadWorkerForOwnMailbox(
|
|
||||||
connectivityChecker, reachabilityMonitor, properties);
|
|
||||||
downloadWorker = toStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toStart != null) toStart.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deassignContactForDownload(ContactId contactId) {
|
|
||||||
LOG.info("Contact deassigned for download");
|
|
||||||
// If there are no more contacts assigned for download, destroy the
|
|
||||||
// download worker
|
|
||||||
MailboxWorker toDestroy = null;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (!assignedForDownload.remove(contactId)) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
if (assignedForDownload.isEmpty()) {
|
|
||||||
toDestroy = downloadWorker;
|
|
||||||
downloadWorker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toDestroy != null) toDestroy.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkConnectivity() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (destroyed) return;
|
|
||||||
connectivityTask = null;
|
|
||||||
}
|
|
||||||
connectivityChecker.checkConnectivity(properties, this);
|
|
||||||
// Avoid leaking observer in case destroy() is called concurrently
|
|
||||||
// before observer is added
|
|
||||||
boolean removeObserver;
|
|
||||||
synchronized (lock) {
|
|
||||||
removeObserver = destroyed;
|
|
||||||
}
|
|
||||||
if (removeObserver) connectivityChecker.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectivityCheckSucceeded() {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (destroyed) return;
|
|
||||||
connectivityTask = taskScheduler.schedule(this::checkConnectivity,
|
|
||||||
ioExecutor, CONNECTIVITY_CHECK_INTERVAL_MS, MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(OwnMailboxConnectivityChecker.class.getName());
|
|
||||||
|
|
||||||
private final MailboxApi mailboxApi;
|
|
||||||
private final TransactionManager db;
|
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
OwnMailboxConnectivityChecker(Clock clock,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
TransactionManager db,
|
|
||||||
MailboxSettingsManager mailboxSettingsManager) {
|
|
||||||
super(clock, mailboxApiCaller);
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
this.db = db;
|
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
return () -> {
|
|
||||||
try {
|
|
||||||
return checkConnectivityAndStoreResult(properties);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return true; // Retry
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkConnectivityAndStoreResult(
|
|
||||||
MailboxProperties properties) throws DbException {
|
|
||||||
try {
|
|
||||||
LOG.info("Checking whether own mailbox is reachable");
|
|
||||||
List<MailboxVersion> serverSupports =
|
|
||||||
mailboxApi.getServerSupports(properties);
|
|
||||||
LOG.info("Own mailbox is reachable");
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
db.transaction(false, txn -> mailboxSettingsManager
|
|
||||||
.recordSuccessfulConnection(txn, now, serverSupports));
|
|
||||||
// Call the observers and cache the result
|
|
||||||
onConnectivityCheckSucceeded(now);
|
|
||||||
return false; // Don't retry
|
|
||||||
} catch (IOException | ApiException e) {
|
|
||||||
LOG.warning("Own mailbox is unreachable");
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
db.transaction(false, txn -> mailboxSettingsManager
|
|
||||||
.recordFailedConnectionAttempt(txn, now));
|
|
||||||
}
|
|
||||||
return true; // Retry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
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.nullsafety.NullSafety.requireNonNull;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class OwnMailboxContactListWorker
|
|
||||||
implements MailboxWorker, ConnectivityObserver, EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the worker is started it waits for a connectivity check, then
|
|
||||||
* fetches the remote contact list and compares it to the local contact
|
|
||||||
* list.
|
|
||||||
* <p>
|
|
||||||
* Any contacts that are missing from the remote list are added to the
|
|
||||||
* mailbox's contact list, while any contacts that are missing from the
|
|
||||||
* local list are removed from the mailbox's contact list.
|
|
||||||
* <p>
|
|
||||||
* Once the remote contact list has been brought up to date, the worker
|
|
||||||
* waits for events indicating that contacts have been added or removed.
|
|
||||||
* Each time an event is received, the worker updates the mailbox's
|
|
||||||
* contact list and then goes back to waiting.
|
|
||||||
*/
|
|
||||||
private enum State {
|
|
||||||
CREATED,
|
|
||||||
CONNECTIVITY_CHECK,
|
|
||||||
FETCHING_CONTACT_LIST,
|
|
||||||
UPDATING_CONTACT_LIST,
|
|
||||||
WAITING_FOR_CHANGES,
|
|
||||||
DESTROYED
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(OwnMailboxContactListWorker.class.getName());
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final DatabaseComponent db;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final ConnectivityChecker connectivityChecker;
|
|
||||||
private final MailboxApiCaller mailboxApiCaller;
|
|
||||||
private final MailboxApi mailboxApi;
|
|
||||||
private final MailboxUpdateManager mailboxUpdateManager;
|
|
||||||
private final MailboxProperties mailboxProperties;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private State state = State.CREATED;
|
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
|
||||||
private Cancellable apiCall = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A queue of updates waiting to be applied to the remote contact list.
|
|
||||||
*/
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private final Queue<Update> updates = new LinkedList<>();
|
|
||||||
|
|
||||||
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
|
|
||||||
DatabaseComponent db,
|
|
||||||
EventBus eventBus,
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxUpdateManager mailboxUpdateManager,
|
|
||||||
MailboxProperties mailboxProperties) {
|
|
||||||
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.db = db;
|
|
||||||
this.connectivityChecker = connectivityChecker;
|
|
||||||
this.mailboxApiCaller = mailboxApiCaller;
|
|
||||||
this.mailboxApi = mailboxApi;
|
|
||||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
|
||||||
this.mailboxProperties = mailboxProperties;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
LOG.info("Started");
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CREATED) return;
|
|
||||||
state = State.CONNECTIVITY_CHECK;
|
|
||||||
}
|
|
||||||
// Avoid leaking observer in case destroy() is called concurrently
|
|
||||||
// before observer is added
|
|
||||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
|
||||||
boolean destroyed;
|
|
||||||
synchronized (lock) {
|
|
||||||
destroyed = state == State.DESTROYED;
|
|
||||||
}
|
|
||||||
if (destroyed) connectivityChecker.removeObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
LOG.info("Destroyed");
|
|
||||||
Cancellable apiCall;
|
|
||||||
synchronized (lock) {
|
|
||||||
state = State.DESTROYED;
|
|
||||||
apiCall = this.apiCall;
|
|
||||||
this.apiCall = null;
|
|
||||||
}
|
|
||||||
if (apiCall != null) apiCall.cancel();
|
|
||||||
connectivityChecker.removeObserver(this);
|
|
||||||
eventBus.removeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectivityCheckSucceeded() {
|
|
||||||
LOG.info("Connectivity check succeeded");
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.CONNECTIVITY_CHECK) return;
|
|
||||||
state = State.FETCHING_CONTACT_LIST;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
new SimpleApiCall(this::apiCallFetchContactList));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void apiCallFetchContactList() throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
|
||||||
}
|
|
||||||
LOG.info("Fetching remote contact list");
|
|
||||||
Collection<ContactId> remote =
|
|
||||||
mailboxApi.getContacts(mailboxProperties);
|
|
||||||
ioExecutor.execute(() -> loadLocalContactList(remote));
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void loadLocalContactList(Collection<ContactId> remote) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
|
||||||
apiCall = null;
|
|
||||||
}
|
|
||||||
LOG.info("Loading local contact list");
|
|
||||||
try {
|
|
||||||
db.transaction(true, txn -> {
|
|
||||||
Collection<Contact> local = db.getContacts(txn);
|
|
||||||
// Handle the result on the event executor to avoid races with
|
|
||||||
// incoming events
|
|
||||||
txn.attach(() -> reconcileContactLists(local, remote));
|
|
||||||
});
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void reconcileContactLists(Collection<Contact> local,
|
|
||||||
Collection<ContactId> remote) {
|
|
||||||
Set<ContactId> localIds = new HashSet<>();
|
|
||||||
for (Contact c : local) localIds.add(c.getId());
|
|
||||||
remote = new HashSet<>(remote);
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
|
||||||
for (ContactId c : localIds) {
|
|
||||||
if (!remote.contains(c)) updates.add(new Update(true, c));
|
|
||||||
}
|
|
||||||
for (ContactId c : remote) {
|
|
||||||
if (!localIds.contains(c)) updates.add(new Update(false, c));
|
|
||||||
}
|
|
||||||
if (updates.isEmpty()) {
|
|
||||||
LOG.info("Contact list is up to date");
|
|
||||||
state = State.WAITING_FOR_CHANGES;
|
|
||||||
} else {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(updates.size() + " updates to apply");
|
|
||||||
}
|
|
||||||
state = State.UPDATING_CONTACT_LIST;
|
|
||||||
ioExecutor.execute(this::updateContactList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void updateContactList() {
|
|
||||||
Update update;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
update = updates.poll();
|
|
||||||
if (update == null) {
|
|
||||||
LOG.info("No more updates to process");
|
|
||||||
state = State.WAITING_FOR_CHANGES;
|
|
||||||
apiCall = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (update.add) loadMailboxProperties(update.contactId);
|
|
||||||
else removeContact(update.contactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void loadMailboxProperties(ContactId c) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
}
|
|
||||||
LOG.info("Loading mailbox properties for contact");
|
|
||||||
try {
|
|
||||||
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
|
|
||||||
mailboxUpdateManager.getLocalUpdate(txn, c));
|
|
||||||
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
|
|
||||||
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
|
|
||||||
} else {
|
|
||||||
// Our own mailbox was concurrently unpaired. This worker will
|
|
||||||
// be destroyed soon, so we can stop here
|
|
||||||
LOG.info("Own mailbox was unpaired");
|
|
||||||
}
|
|
||||||
} catch (NoSuchContactException e) {
|
|
||||||
// Contact was removed concurrently. Move on to the next update.
|
|
||||||
// Later we may process a removal update for this contact, which
|
|
||||||
// was never added to the mailbox's contact list. The removal API
|
|
||||||
// call should fail safely with a TolerableFailureException
|
|
||||||
LOG.info("No such contact");
|
|
||||||
updateContactList();
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
|
|
||||||
MailboxProperties props = withMailbox.getMailboxProperties();
|
|
||||||
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
|
|
||||||
requireNonNull(props.getInboxId()),
|
|
||||||
requireNonNull(props.getOutboxId()));
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
|
||||||
apiCallAddContact(contact)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void apiCallAddContact(MailboxContact contact)
|
|
||||||
throws IOException, ApiException, TolerableFailureException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
}
|
|
||||||
LOG.info("Adding contact to remote contact list");
|
|
||||||
mailboxApi.addContact(mailboxProperties, contact);
|
|
||||||
updateContactList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void removeContact(ContactId c) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
|
||||||
apiCallRemoveContact(c)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void apiCallRemoveContact(ContactId c)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
|
||||||
}
|
|
||||||
LOG.info("Removing contact from remote contact list");
|
|
||||||
try {
|
|
||||||
mailboxApi.deleteContact(mailboxProperties, c);
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
// Catch this so we can continue to the next update
|
|
||||||
LOG.warning("Contact does not exist");
|
|
||||||
}
|
|
||||||
updateContactList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof ContactAddedEvent) {
|
|
||||||
LOG.info("Contact added");
|
|
||||||
onContactAdded(((ContactAddedEvent) e).getContactId());
|
|
||||||
} else if (e instanceof ContactRemovedEvent) {
|
|
||||||
LOG.info("Contact removed");
|
|
||||||
onContactRemoved(((ContactRemovedEvent) e).getContactId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactAdded(ContactId c) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST &&
|
|
||||||
state != State.WAITING_FOR_CHANGES) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updates.add(new Update(true, c));
|
|
||||||
if (state == State.WAITING_FOR_CHANGES) {
|
|
||||||
state = State.UPDATING_CONTACT_LIST;
|
|
||||||
ioExecutor.execute(this::updateContactList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventExecutor
|
|
||||||
private void onContactRemoved(ContactId c) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state != State.UPDATING_CONTACT_LIST &&
|
|
||||||
state != State.WAITING_FOR_CHANGES) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updates.add(new Update(false, c));
|
|
||||||
if (state == State.WAITING_FOR_CHANGES) {
|
|
||||||
state = State.UPDATING_CONTACT_LIST;
|
|
||||||
ioExecutor.execute(this::updateContactList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An update that should be applied to the remote contact list.
|
|
||||||
*/
|
|
||||||
private static class Update {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the contact should be added, false if the contact should be
|
|
||||||
* removed.
|
|
||||||
*/
|
|
||||||
private final boolean add;
|
|
||||||
private final ContactId contactId;
|
|
||||||
|
|
||||||
private Update(boolean add, ContactId contactId) {
|
|
||||||
this.add = add;
|
|
||||||
this.contactId = contactId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.Collections.shuffle;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
|
|
||||||
@ThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class OwnMailboxDownloadWorker extends MailboxDownloadWorker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of files that will be downloaded before checking
|
|
||||||
* again for folders with available files. This ensures that if a file
|
|
||||||
* arrives during a download cycle, its folder will be checked within a
|
|
||||||
* reasonable amount of time even if some other folder has a very large
|
|
||||||
* number of files.
|
|
||||||
* <p>
|
|
||||||
* Package access for testing.
|
|
||||||
*/
|
|
||||||
static final int MAX_ROUND_ROBIN_FILES = 1000;
|
|
||||||
|
|
||||||
OwnMailboxDownloadWorker(
|
|
||||||
ConnectivityChecker connectivityChecker,
|
|
||||||
TorReachabilityMonitor torReachabilityMonitor,
|
|
||||||
MailboxApiCaller mailboxApiCaller,
|
|
||||||
MailboxApi mailboxApi,
|
|
||||||
MailboxFileManager mailboxFileManager,
|
|
||||||
MailboxProperties mailboxProperties) {
|
|
||||||
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
|
||||||
mailboxApi, mailboxFileManager, mailboxProperties);
|
|
||||||
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ApiCall createApiCallForDownloadCycle() {
|
|
||||||
return new SimpleApiCall(this::apiCallListFolders);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apiCallListFolders() throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
LOG.info("Listing folders with available files");
|
|
||||||
List<MailboxFolderId> folders =
|
|
||||||
mailboxApi.getFolders(mailboxProperties);
|
|
||||||
if (folders.isEmpty()) onDownloadCycleFinished();
|
|
||||||
else listNextFolder(new LinkedList<>(folders), new HashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the next folder from `queue` and starts a task to list the
|
|
||||||
* files in the folder and add them to `available`.
|
|
||||||
*/
|
|
||||||
private void listNextFolder(Queue<MailboxFolderId> queue,
|
|
||||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
MailboxFolderId folder = queue.remove();
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
|
||||||
apiCallListFolder(folder, queue, available)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apiCallListFolder(MailboxFolderId folder,
|
|
||||||
Queue<MailboxFolderId> queue,
|
|
||||||
Map<MailboxFolderId, Queue<MailboxFile>> available)
|
|
||||||
throws IOException, ApiException {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
LOG.info("Listing folder");
|
|
||||||
try {
|
|
||||||
List<MailboxFile> files =
|
|
||||||
mailboxApi.getFiles(mailboxProperties, folder);
|
|
||||||
if (!files.isEmpty()) {
|
|
||||||
available.put(folder, new LinkedList<>(files));
|
|
||||||
}
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
LOG.warning("Folder does not exist");
|
|
||||||
}
|
|
||||||
if (queue.isEmpty()) {
|
|
||||||
LOG.info("Finished listing folders");
|
|
||||||
if (available.isEmpty()) onDownloadCycleFinished();
|
|
||||||
else createDownloadQueue(available);
|
|
||||||
} else {
|
|
||||||
listNextFolder(queue, available);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visits the given folders in round-robin order to create a queue of up to
|
|
||||||
* {@link #MAX_ROUND_ROBIN_FILES} to download.
|
|
||||||
*/
|
|
||||||
private void createDownloadQueue(
|
|
||||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(available.size() + " folders have available files");
|
|
||||||
}
|
|
||||||
Queue<FolderFile> queue = createRoundRobinQueue(available);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Downloading " + queue.size() + " files");
|
|
||||||
}
|
|
||||||
downloadNextFile(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package access for testing
|
|
||||||
Queue<FolderFile> createRoundRobinQueue(
|
|
||||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
|
||||||
List<MailboxFolderId> roundRobin = new ArrayList<>(available.keySet());
|
|
||||||
// Shuffle the folders so we don't always favour the same folders
|
|
||||||
shuffle(roundRobin);
|
|
||||||
Queue<FolderFile> queue = new LinkedList<>();
|
|
||||||
while (queue.size() < MAX_ROUND_ROBIN_FILES && !available.isEmpty()) {
|
|
||||||
Iterator<MailboxFolderId> it = roundRobin.iterator();
|
|
||||||
while (queue.size() < MAX_ROUND_ROBIN_FILES && it.hasNext()) {
|
|
||||||
MailboxFolderId folder = it.next();
|
|
||||||
Queue<MailboxFile> files = available.get(folder);
|
|
||||||
MailboxFile file = files.remove();
|
|
||||||
queue.add(new FolderFile(folder, file.name));
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
available.remove(folder);
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return queue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience class for making simple API calls that don't return values.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class SimpleApiCall implements ApiCall {
|
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
|
||||||
|
|
||||||
private final Attempt attempt;
|
|
||||||
|
|
||||||
SimpleApiCall(Attempt attempt) {
|
|
||||||
this.attempt = attempt;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean callApi() {
|
|
||||||
try {
|
|
||||||
attempt.tryToCallApi();
|
|
||||||
return false; // Succeeded, don't retry
|
|
||||||
} catch (IOException | ApiException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return true; // Failed, retry with backoff
|
|
||||||
} catch (TolerableFailureException e) {
|
|
||||||
logException(LOG, INFO, e);
|
|
||||||
return false; // Failed tolerably, don't retry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Attempt {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a single attempt to call an API endpoint. If this method
|
|
||||||
* throws an {@link IOException} or an {@link ApiException}, the call
|
|
||||||
* will be retried. If it throws a {@link TolerableFailureException}
|
|
||||||
* or returns without throwing an exception, the call will not be
|
|
||||||
* retried.
|
|
||||||
*/
|
|
||||||
void tryToCallApi()
|
|
||||||
throws IOException, ApiException, TolerableFailureException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user