Compare commits

..

3 Commits

Author SHA1 Message Date
Sebastian Kürten
b6fa7520e9 Try printing db table sizes on startup 2022-05-09 09:16:09 +02:00
Daniel Lublin
d3bffaadf3 Fetch and store mailbox's supported api versions when pairing 2022-04-16 13:49:53 +02:00
Daniel Lublin
12b887881d Allow storing int array in settings 2022-04-16 13:49:52 +02:00
273 changed files with 3617 additions and 12562 deletions

View File

@@ -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

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10409 versionCode 10406
versionName "1.4.9" versionName "1.4.6"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

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

View File

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

View File

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

View File

@@ -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;
} }
} }

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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);
}
} }

View File

@@ -33,18 +33,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/** /**
* Encapsulates the database implementation and exposes high-level operations * Encapsulates the database implementation and exposes high-level operations
* to other components. * to other components.
* <p>
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
* {@link #close()} methods, which must not be called concurrently, the
* database can be accessed from any thread. See {@link TransactionManager}
* for locking behaviour.
*/ */
@ThreadSafe
@NotNullByDefault @NotNullByDefault
public interface DatabaseComponent extends TransactionManager { public interface DatabaseComponent extends TransactionManager {
@@ -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.
@@ -348,30 +344,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g, Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
Metadata query) throws DbException; Metadata query) throws DbException;
/**
* Returns the IDs of some messages received from the given contact that
* need to be acknowledged, up to the given number of messages.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
int maxMessages) throws DbException;
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact over a transport with the given maximum latency. The total
* length of the messages including record headers will be no more than the
* given capacity.
* <p/>
* Unlike {@link #getUnackedMessagesToSend(Transaction, ContactId)} this
* method does not return messages that have already been sent unless they
* are due for retransmission.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToSend(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
* <p/> * <p/>
@@ -488,30 +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.
*
* @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;
/** /**
@@ -541,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.
@@ -694,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.
@@ -747,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
*/ */
@@ -806,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;
} }

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -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 {
}

View File

@@ -1,35 +0,0 @@
package org.briarproject.bramble.api.mailbox;
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;
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.
*/
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;
}
}

View File

@@ -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;
} }

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -15,36 +14,13 @@ public class MailboxProperties {
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
/**
* Constructor for properties used by the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) { boolean owner, List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = true; this.owner = owner;
this.serverSupports = serverSupports; this.serverSupports = serverSupports;
this.inboxId = null;
this.outboxId = null;
}
/**
* Constructor for properties used by a contact of the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = false;
this.serverSupports = serverSupports;
this.inboxId = inboxId;
this.outboxId = outboxId;
} }
public String getBaseUrl() { public String getBaseUrl() {
@@ -67,14 +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;
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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,16 +26,11 @@ 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)
throws DbException; throws DbException;
void recordSuccessfulConnection(Transaction txn, long now,
List<MailboxVersion> versions) throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now) void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException; throws DbException;
@@ -54,8 +47,7 @@ public interface MailboxSettingsManager {
* @param txn A read-write transaction * @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars) * @param ownOnion Our new mailbox's onion (56 base32 chars)
*/ */
void mailboxPaired(Transaction txn, String ownOnion, void mailboxPaired(Transaction txn, String ownOnion)
List<MailboxVersion> serverSupports)
throws DbException; throws DbException;
/** /**

View File

@@ -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,21 +56,4 @@ public class MailboxStatus {
public int getAttemptsSinceSuccess() { public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess; return attemptsSinceSuccess;
} }
/**
* @return true if this status indicates a problem with the mailbox.
*/
public boolean hasProblem(long now) {
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
}
/**
* @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);
}
} }

View File

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

View File

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

View File

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

View File

@@ -1,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;

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -1,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";

View File

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

View File

@@ -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;
}
}

View File

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

View File

@@ -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);

View File

@@ -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.
*/ */

View File

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

View File

@@ -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());
} }

View File

@@ -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;
}
}

View File

@@ -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 baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) {
return new MailboxProperties(baseUrl, authToken, serverSupports);
}
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId);
}
public static void writeBytes(File file, byte[] bytes) 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,13 +314,4 @@ public class TestUtils {
} }
return false; return false;
} }
public static boolean isCryptoStrengthUnlimited() {
try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
== Integer.MAX_VALUE;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError();
}
}
} }

View File

@@ -16,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.4' implementation 'org.briarproject:jtorctl:0.3'
//noinspection GradleDependency //noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@@ -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,13 +39,12 @@ 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;
@@ -56,12 +52,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT; import static org.briarproject.bramble.api.mailbox.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;
@@ -416,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) {
@@ -456,26 +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);
String baseUrl = "http://" + onion + ".onion"; // TODO return new MailboxPropertiesUpdate(onion,
MailboxProperties props = new MailboxProperties(baseUrl, new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
new MailboxAuthToken(authToken), serverSupportsList, new MailboxFolderId(outboxId));
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props);
}
@Override
public List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException {
List<MailboxVersion> list = new ArrayList<>();
for (int i = 0; i < bdfList.size(); i++) {
BdfList element = bdfList.getList(i);
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
}
return list;
} }
@Override @Override

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}
} }
} }

View File

@@ -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);
}
} }
} }

View File

@@ -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/>
@@ -413,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/>
@@ -509,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
@@ -518,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;
/** /**
@@ -587,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.
@@ -615,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.
@@ -880,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;
} }

View File

@@ -75,6 +75,7 @@ import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -86,7 +87,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -342,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
@@ -373,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 {
@@ -433,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) {
@@ -449,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,
@@ -488,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) {
@@ -504,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;
@@ -618,24 +635,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageIds(txn, g, query); return db.getMessageIds(txn, g, query);
} }
@Override
public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, maxMessages);
}
@Override
public Collection<MessageId> getMessagesToSend(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToSend(txn, c, capacity, maxLatency);
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction) public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException { throws DbException {
@@ -741,29 +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 (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();
@@ -814,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
@@ -1025,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
@@ -1090,20 +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();
List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
}
@Override @Override
public void setCleanupTimerDuration(Transaction transaction, MessageId m, public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException { long duration) throws DbException {
@@ -1150,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
@@ -1198,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)
@@ -1313,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

View File

@@ -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;

View File

@@ -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;

View File

@@ -51,6 +51,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -69,14 +70,12 @@ import static java.sql.Types.BOOLEAN;
import static java.sql.Types.INTEGER; import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR; import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE; import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED; import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -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 {
@@ -1930,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 {
@@ -2303,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;
@@ -2324,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();
@@ -2341,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"
@@ -2355,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);
@@ -2472,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);
@@ -2490,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"
@@ -2615,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;
@@ -2635,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();
@@ -2793,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);

View File

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

View File

@@ -190,10 +190,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
return; return;
} }
try { try {
if (state == STOPPING) {
LOG.info("Already stopped");
return;
}
LOG.info("Stopping services"); LOG.info("Stopping services");
state = STOPPING; state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING)); eventBus.broadcast(new LifecycleEvent(STOPPING));

View File

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

View File

@@ -1,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();
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -1,122 +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 javax.inject.Inject;
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;
@Inject
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();
}
@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();
}
}

View File

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

View File

@@ -1,243 +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.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
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.LinkedList;
import java.util.List;
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.Level.INFO;
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 ContactMailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking the inbox, downloading and
* deleting any files, and checking again until the inbox is empty.
* <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.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
private static final Logger LOG =
getLogger(ContactMailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
ContactMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
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(
new SimpleApiCall(this::apiCallListInbox));
}
}
private void apiCallListInbox() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing inbox");
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
if (files.isEmpty()) onDownloadCycleFinished();
else downloadNextFile(new LinkedList<>(files));
}
private 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);
}
}
private void downloadNextFile(Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
}
}
private void apiCallDownloadFile(MailboxFile file,
Queue<MailboxFile> queue) throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file.name, tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file.name);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next file
logException(LOG, INFO, e);
}
if (queue.isEmpty()) {
// List the inbox again to check for files that may have arrived
// while we were downloading
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
} else {
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(
new SimpleApiCall(this::apiCallListInbox));
}
}
}

View File

@@ -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.
* *

View File

@@ -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);
}

View File

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

View File

@@ -56,23 +56,6 @@ class MailboxApiImpl implements MailboxApi {
this.httpClientProvider = httpClientProvider; this.httpClientProvider = httpClientProvider;
} }
@Override
public List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/versions");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
return parseServerSupports(node);
} catch (JacksonException e) {
throw new ApiException();
}
}
@Override @Override
public MailboxProperties setup(MailboxProperties properties) public MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException { throws IOException, ApiException {
@@ -93,40 +76,36 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) { if (tokenNode == null) {
throw new ApiException(); throw new ApiException();
} }
List<MailboxVersion> serverSupports = new ArrayList<>();
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
for (JsonNode versionNode : serverSupportsNode) {
if (!versionNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) versionNode;
JsonNode majorNode = objectNode.get("major");
JsonNode minorNode = objectNode.get("minor");
if (majorNode == null || !majorNode.isNumber()) {
throw new ApiException();
}
if (minorNode == null || !minorNode.isNumber()) {
throw new ApiException();
}
int major = majorNode.asInt();
int minor = minorNode.asInt();
if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor));
}
return new MailboxProperties(properties.getBaseUrl(), 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));
}
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();

View File

@@ -1,46 +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.
*/
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.
*/
void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for download.
*/
void deassignContactForDownload(ContactId c);
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
} }

View File

@@ -1,42 +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.event.EventBus;
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.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 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;
} }
@Provides @Provides
@@ -52,7 +44,6 @@ public class MailboxModule {
} }
@Provides @Provides
@Singleton
MailboxSettingsManager provideMailboxSettingsManager( MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) { MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager; return mailboxSettingsManager;
@@ -65,60 +56,31 @@ public class MailboxModule {
@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;
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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();
} }
@@ -125,9 +125,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
// 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());
} }
} }
@@ -177,10 +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"; // TODO 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(baseUrl, setupToken, new ArrayList<>()); return new MailboxProperties(baseUrl, setupToken, true,
new ArrayList<>());
} }
} }

View File

@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook; import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser; import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -19,13 +18,11 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
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.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.MailboxSettingsManager.MailboxHook; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
@@ -38,7 +35,6 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -48,11 +44,10 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
@NotNullByDefault @NotNullByDefault
class MailboxUpdateManagerImpl implements MailboxUpdateManager, class MailboxPropertyManagerImpl implements MailboxPropertyManager,
OpenDatabaseHook, ContactHook, ClientVersioningHook, OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook, MailboxHook { IncomingMessageHook, MailboxHook {
private final List<MailboxVersion> clientSupports;
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
@@ -64,14 +59,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
private final Group localGroup; private final Group localGroup;
@Inject @Inject
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports, MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock, ContactGroupFactory contactGroupFactory, Clock clock,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
CryptoComponent crypto) { CryptoComponent crypto) {
this.clientSupports = clientSupports;
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.clientVersioningManager = clientVersioningManager; this.clientVersioningManager = clientVersioningManager;
@@ -87,46 +80,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
@Override @Override
public void onDatabaseOpened(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) { if (db.containsGroup(txn, localGroup.getId())) {
try { return;
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);
}
} }
db.addGroup(txn, localGroup);
try { // Set things up for any pre-existing contacts
BdfDictionary meta = BdfDictionary.of(new BdfEntry( for (Contact c : db.getContacts(txn)) {
GROUP_KEY_SENT_CLIENT_SUPPORTS, addingContact(txn, c);
encodeSupportsList(clientSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
} }
} }
@@ -141,15 +100,11 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
db.setGroupVisibility(txn, c.getId(), g.getId(), client); db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group // Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId()); clientHelper.setContactId(txn, g.getId(), c.getId());
// If we are paired, create and send props to the newly added contact
MailboxProperties ownProps = MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn); mailboxSettingsManager.getOwnMailboxProperties(txn);
if (ownProps != null) { if (ownProps != null) {
// We are paired, create and send props to the newly added contact createAndSendProperties(txn, c, ownProps.getOnion());
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getOnion());
} else {
// Not paired, but we still want to get our clientSupports sent
sendUpdateNoMailbox(txn, c);
} }
} }
@@ -159,17 +114,17 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
} }
@Override @Override
public void mailboxPaired(Transaction txn, String ownOnion, public void mailboxPaired(Transaction txn, String ownOnion)
List<MailboxVersion> serverSupports) throws DbException { throws DbException {
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion); createAndSendProperties(txn, c, ownOnion);
} }
} }
@Override @Override
public void mailboxUnpaired(Transaction txn) throws DbException { public void mailboxUnpaired(Transaction txn) throws DbException {
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
sendUpdateNoMailbox(txn, c); sendEmptyProperties(txn, c);
} }
} }
@@ -201,8 +156,8 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
} }
ContactId c = clientHelper.getContactId(txn, m.getGroupId()); ContactId c = clientHelper.getContactId(txn, m.getGroupId());
BdfList body = clientHelper.getMessageAsList(txn, m.getId()); BdfList body = clientHelper.getMessageAsList(txn, m.getId());
MailboxUpdate u = parseUpdate(body); MailboxPropertiesUpdate p = parseProperties(body);
txn.attach(new RemoteMailboxUpdateEvent(c, u)); txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
// Reset message retransmission timers for the contact. Avoiding // Reset message retransmission timers for the contact. Avoiding
// messages getting stranded: // messages getting stranded:
// - on our mailbox, if they now have a mailbox but didn't before // - on our mailbox, if they now have a mailbox but didn't before
@@ -216,82 +171,70 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
} }
@Override @Override
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c) @Nullable
throws DbException { public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true); ContactId c) throws DbException {
// An update (with or without mailbox) is created when contact is added return getProperties(txn, db.getContact(txn, c), true);
if (local == null) {
throw new DbException();
}
return local;
} }
@Override @Override
@Nullable @Nullable
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c) public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
throws DbException { ContactId c) throws DbException {
return getUpdate(txn, db.getContact(txn, c), false); return getProperties(txn, db.getContact(txn, c), false);
} }
/** /**
* Creates and sends an update message to the given contact. The message * Creates and sends an update message to the given contact. The message
* holds our own mailbox's onion, generated unique properties, and lists of * holds our own mailbox's onion, and generated unique properties. All of
* supported Mailbox API version(s). All of which the contact needs to * which the contact needs to communicate with our Mailbox.
* communicate with our Mailbox.
*/ */
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c, private void createAndSendProperties(Transaction txn,
List<MailboxVersion> serverSupports, String ownOnion) Contact c, String ownOnion) throws DbException {
throws DbException { MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()), new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()), new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes())); new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c); Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u); storeMessageReplaceLatest(txn, g.getId(), p);
} }
/** /**
* Sends an update message with empty properties to the given contact. The * Sends an empty update message to the given contact. The empty update
* empty update indicates for the receiving contact that we don't have any * indicates for the receiving contact that we no longer have a Mailbox that
* Mailbox that they can use. It still includes the list of Mailbox API * they can use.
* version(s) that we support as a client.
*/ */
private void sendUpdateNoMailbox(Transaction txn, Contact c) private void sendEmptyProperties(Transaction txn, Contact c)
throws DbException { throws DbException {
Group g = getContactGroup(c); Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports); storeMessageReplaceLatest(txn, g.getId(), null);
storeMessageReplaceLatest(txn, g.getId(), u);
} }
@Nullable @Nullable
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local) private MailboxPropertiesUpdate getProperties(Transaction txn,
throws DbException { Contact c, boolean local) throws DbException {
MailboxUpdate u = null; MailboxPropertiesUpdate p = null;
Group g = getContactGroup(c); Group g = getContactGroup(c);
try { try {
LatestUpdate latest = findLatest(txn, g.getId(), local); LatestUpdate latest = findLatest(txn, g.getId(), local);
if (latest != null) { if (latest != null) {
BdfList body = BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId); clientHelper.getMessageAsList(txn, latest.messageId);
u = parseUpdate(body); p = parseProperties(body);
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
return u; return p;
} }
private void storeMessageReplaceLatest(Transaction txn, GroupId g, private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u) throws DbException { @Nullable MailboxPropertiesUpdate p) throws DbException {
try { try {
LatestUpdate latest = findLatest(txn, g, true); LatestUpdate latest = findLatest(txn, g, true);
long version = latest == null ? 1 : latest.version + 1; long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(), Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u)); encodeProperties(version, p));
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version); meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true); meta.put(MSG_KEY_LOCAL, true);
@@ -323,36 +266,23 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
return null; return null;
} }
private BdfList encodeProperties(long version, MailboxUpdate u) { private BdfList encodeProperties(long version,
@Nullable MailboxPropertiesUpdate p) {
BdfDictionary dict = new BdfDictionary(); BdfDictionary dict = new BdfDictionary();
BdfList serverSupports = new BdfList(); if (p != null) {
if (u.hasMailbox()) { dict.put(PROP_KEY_ONION, p.getOnion());
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u; dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
MailboxProperties properties = um.getMailboxProperties(); dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
serverSupports = encodeSupportsList(properties.getServerSupports()); dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
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()), return BdfList.of(version, dict);
serverSupports, dict);
} }
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) { @Nullable
BdfList supports = new BdfList(); private MailboxPropertiesUpdate parseProperties(BdfList body)
for (MailboxVersion version : supportsList) { throws FormatException {
supports.add(BdfList.of(version.getMajor(), version.getMinor())); BdfDictionary dict = body.getDictionary(1);
} return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
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) { private Group getContactGroup(Contact c) {

View File

@@ -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);

View File

@@ -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);
} }
@@ -77,21 +83,16 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
s.put(SETTINGS_KEY_ONION, p.getBaseUrl()); 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.getOnion(), p.getServerSupports()); 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, "");
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());
} }
} }
@@ -102,44 +103,18 @@ 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)
throws DbException { throws DbException {
recordSuccessfulConnection(txn, now, null);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now,
@Nullable List<MailboxVersion> versions) throws DbException {
Settings s = new Settings(); Settings s = new Settings();
// fetch version that the server supports first
List<MailboxVersion> serverSupports;
if (versions == null) {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
serverSupports = parseServerSupports(oldSettings);
} else {
serverSupports = versions;
// store new versions
encodeServerSupports(serverSupports, s);
}
// now 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);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
// broadcast status event MailboxStatus status = new MailboxStatus(now, now, 0);
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
txn.attach(new OwnMailboxConnectionStatusEvent(status)); txn.attach(new OwnMailboxConnectionStatusEvent(status));
} }
@@ -154,11 +129,8 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
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
@@ -180,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;
}
} }

View File

@@ -1,394 +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.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.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.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.
*/
private enum State {
CREATED,
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 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,
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.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;
}
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);
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
apiCall = null;
this.file = null;
}
delete(file);
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) {
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();
}
}
}
@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();
}
}

View File

@@ -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();
}

View File

@@ -1,27 +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);
}

View File

@@ -1,81 +0,0 @@
package org.briarproject.bramble.mailbox;
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.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 MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
@Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
}
@Override
public MailboxWorker createUploadWorker(
ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, 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) {
// TODO
throw new UnsupportedOperationException();
}
}

View File

@@ -1,78 +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 static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(OwnMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
return () -> {
try {
return checkConnectivityAndStoreResult(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
return true; // Retry
}
};
}
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
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
}
}

View File

@@ -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
logException(LOG, INFO, e);
}
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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,51 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MINUTES;
@ThreadSafe
@NotNullByDefault
interface TorReachabilityMonitor {
/**
* How long the Tor plugin needs to be continuously
* {@link Plugin.State#ACTIVE active} before we assume our contacts can
* reach our hidden service.
*/
long REACHABILITY_PERIOD_MS = MINUTES.toMillis(10);
/**
* Starts the monitor.
*/
void start();
/**
* Destroys the monitor.
*/
void destroy();
/**
* Adds an observer that will be called when our Tor hidden service becomes
* reachable. If our hidden service is already reachable, the observer is
* called immediately.
* <p>
* Observers are removed after being called, or when the monitor is
* {@link #destroy() destroyed}.
*/
void addOneShotObserver(TorReachabilityObserver o);
/**
* Removes an observer that was added via
* {@link #addOneShotObserver(TorReachabilityObserver)}.
*/
void removeObserver(TorReachabilityObserver o);
interface TorReachabilityObserver {
void onTorReachable();
}
}

View File

@@ -1,137 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
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.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 org.briarproject.bramble.api.system.TaskScheduler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
@ThreadSafe
@NotNullByDefault
class TorReachabilityMonitorImpl
implements TorReachabilityMonitor, EventListener {
private final Executor ioExecutor;
private final TaskScheduler taskScheduler;
private final PluginManager pluginManager;
private final EventBus eventBus;
private final Object lock = new Object();
@GuardedBy("lock")
private boolean reachable = false, destroyed = false;
@GuardedBy("lock")
private final List<TorReachabilityObserver> observers = new ArrayList<>();
@GuardedBy("lock")
@Nullable
private Cancellable task = null;
@Inject
TorReachabilityMonitorImpl(
@IoExecutor Executor ioExecutor,
TaskScheduler taskScheduler,
PluginManager pluginManager,
EventBus eventBus) {
this.ioExecutor = ioExecutor;
this.taskScheduler = taskScheduler;
this.pluginManager = pluginManager;
this.eventBus = eventBus;
}
@Override
public void start() {
eventBus.addListener(this);
Plugin plugin = pluginManager.getPlugin(ID);
if (plugin != null && plugin.getState() == ACTIVE) onTorActive();
}
@Override
public void destroy() {
eventBus.removeListener(this);
synchronized (lock) {
destroyed = true;
if (task != null) task.cancel();
task = null;
observers.clear();
}
}
@Override
public void addOneShotObserver(TorReachabilityObserver o) {
boolean callNow = false;
synchronized (lock) {
if (destroyed) return;
if (reachable) callNow = true;
else observers.add(o);
}
if (callNow) o.onTorReachable();
}
@Override
public void removeObserver(TorReachabilityObserver o) {
synchronized (lock) {
if (destroyed) return;
observers.remove(o);
}
}
@Override
public void eventOccurred(Event e) {
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();
}
}
private void onTorActive() {
synchronized (lock) {
if (destroyed || task != null) return;
task = taskScheduler.schedule(this::onTorReachable, ioExecutor,
REACHABILITY_PERIOD_MS, MILLISECONDS);
}
}
private void onTorInactive() {
synchronized (lock) {
reachable = false;
if (task != null) task.cancel();
task = null;
}
}
@IoExecutor
private void onTorReachable() {
List<TorReachabilityObserver> observers;
synchronized (lock) {
if (destroyed) return;
reachable = true;
observers = new ArrayList<>(this.observers);
this.observers.clear();
task = null;
}
for (TorReachabilityObserver o : observers) o.onTorReachable();
}
}

View File

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

View File

@@ -67,7 +67,6 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
public void start() { public void start() {
callback.mergeLocalProperties( callback.mergeLocalProperties(
new TransportProperties(singletonMap(PROP_SUPPORTED, "true"))); new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
callback.pluginStateChanged(ACTIVE);
} }
@Override @Override

View File

@@ -11,7 +11,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.io.OutputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -30,6 +29,11 @@ abstract class FilePlugin implements SimplexPlugin {
protected final PluginCallback callback; protected final PluginCallback callback;
protected final long maxLatency; protected final long maxLatency;
protected abstract void writerFinished(File f, boolean exception);
protected abstract void readerFinished(File f, boolean exception,
boolean recognised);
FilePlugin(PluginCallback callback, long maxLatency) { FilePlugin(PluginCallback callback, long maxLatency) {
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
@@ -46,8 +50,9 @@ abstract class FilePlugin implements SimplexPlugin {
String path = p.get(PROP_PATH); String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null; if (isNullOrEmpty(path)) return null;
try { try {
FileInputStream in = new FileInputStream(path); File file = new File(path);
return new TransportInputStreamReader(in); FileInputStream in = new FileInputStream(file);
return new FileTransportReader(file, in, this);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return null; return null;
@@ -65,8 +70,8 @@ abstract class FilePlugin implements SimplexPlugin {
LOG.info("Failed to create file"); LOG.info("Failed to create file");
return null; return null;
} }
OutputStream out = new FileOutputStream(file); FileOutputStream out = new FileOutputStream(file);
return new TransportOutputStreamWriter(this, out); return new FileTransportWriter(file, out, this);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return null; return null;

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.File;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class FileTransportReader implements TransportConnectionReader {
private static final Logger LOG =
Logger.getLogger(FileTransportReader.class.getName());
private final File file;
private final InputStream in;
private final FilePlugin plugin;
FileTransportReader(File file, InputStream in, FilePlugin plugin) {
this.file = file;
this.in = in;
this.plugin = plugin;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public void dispose(boolean exception, boolean recognised) {
tryToClose(in, LOG, WARNING);
plugin.readerFinished(file, exception, recognised);
}
}

View File

@@ -0,0 +1,54 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import java.io.File;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class FileTransportWriter implements TransportConnectionWriter {
private static final Logger LOG =
Logger.getLogger(FileTransportWriter.class.getName());
private final File file;
private final OutputStream out;
private final FilePlugin plugin;
FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
this.file = file;
this.out = out;
this.plugin = plugin;
}
@Override
public long getMaxLatency() {
return plugin.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return plugin.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void dispose(boolean exception) {
tryToClose(out, LOG, WARNING);
plugin.writerFinished(file, exception);
}
}

View File

@@ -1,73 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.Collection;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@NotNullByDefault
class MailboxPlugin extends FilePlugin {
MailboxPlugin(PluginCallback callback, long maxLatency) {
super(callback, maxLatency);
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxIdleTime() {
// Unused for simplex transports
throw new UnsupportedOperationException();
}
@Override
public void start() throws PluginException {
callback.pluginStateChanged(ACTIVE);
}
@Override
public void stop() throws PluginException {
}
@Override
public State getState() {
return ACTIVE;
}
@Override
public int getReasonsDisabled() {
return 0;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLossyAndCheap() {
return false;
}
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
@NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory {
@Inject
MailboxPluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new MailboxPlugin(callback, MAX_LATENCY);
}
}

View File

@@ -106,8 +106,7 @@ class RemovableDriveManagerImpl
@Override @Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException { public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn -> return db.transactionWithResult(true, txn ->
db.containsAcksToSend(txn, c) || db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
} }
@Override @Override

View File

@@ -27,7 +27,7 @@ public interface CircumventionProvider {
* Countries where bridge connections are likely to work. * Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of * Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and * {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}. * {@link #MEEK_BRIDGES}.
*/ */
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"}; String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"}; String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
/** /**
* Countries where vanilla bridges are blocked via DPI but non-default * Countries where obfs4 and vanilla bridges won't work and meek is needed.
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}. * Should be a subset of {@link #BRIDGES}.
*/ */
String[] DPI_BRIDGES = {"CN", "IR"}; String[] MEEK_BRIDGES = {"CN", "IR"};
/** /**
* Returns true if vanilla Tor connections are blocked in the given country. * Returns true if vanilla Tor connections are blocked in the given country.

View File

@@ -14,6 +14,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
@@ -34,8 +35,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(DEFAULT_BRIDGES)); new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES = private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES)); new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES = private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES)); new HashSet<>(asList(MEEK_BRIDGES));
@Inject @Inject
CircumventionProviderImpl() { CircumventionProviderImpl() {
@@ -57,8 +58,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
return asList(DEFAULT_OBFS4, VANILLA); return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) { } else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA); return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) { } else if (MEEK_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK); return singletonList(MEEK);
} else { } else {
return asList(DEFAULT_OBFS4, VANILLA); return asList(DEFAULT_OBFS4, VANILLA);
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.tor;
import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection; import net.freehaven.tor.control.TorControlConnection;
import net.freehaven.tor.control.TorNotRunningException;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
@@ -107,7 +106,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName()); private static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = { private static final String[] EVENTS = {
"CIRC", "CIRC",
@@ -124,8 +123,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor; private final Executor ioExecutor, wakefulIoExecutor;
private final Executor wakefulIoExecutor;
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
@@ -255,15 +253,34 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment(); Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath()); env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory); pb.directory(torDirectory);
pb.redirectErrorStream(true);
try { try {
torProcess = pb.start(); torProcess = pb.start();
} catch (SecurityException | IOException e) { } catch (SecurityException | IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
LOG.info(stderr.nextLine());
}
}
stdout.close();
stderr.close();
}
try { try {
// Wait for the Tor process to start // Wait for the process to detach or exit
waitForTorToStart(torProcess); int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
// Wait for the auth cookie file to be created/updated // Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) { while (cookieFile.length() < 32) {
@@ -304,8 +321,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Tor has already built a circuit"); LOG.info("Tor has already built a circuit");
state.getAndSetCircuitBuilt(true); state.getAndSetCircuitBuilt(true);
} }
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
@@ -379,7 +394,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin; return zin;
} }
private static void append(StringBuilder strb, String name, Object value) { private static void append(StringBuilder strb, String name, int value) {
strb.append(name); strb.append(name);
strb.append(" "); strb.append(" ");
strb.append(value); strb.append(value);
@@ -387,17 +402,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private InputStream getConfigInputStream() { private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder(); StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort); append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1); append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1); append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1); append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1); append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
//noinspection CharsetObjectCanBeUsed //noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream( return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8"))); strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -428,23 +439,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Read the process's stdout (and redirected stderr) until it detaches
while (stdout.hasNextLine()) stdout.nextLine();
stdout.close();
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it // If there's already a port number stored in config, reuse it
@@ -498,8 +492,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} else { } else {
response = controlConnection.addOnion(privKey, portLines); response = controlConnection.addOnion(privKey, portLines);
} }
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return; return;
@@ -548,38 +540,30 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
state.enableNetwork(enable); state.enableNetwork(enable);
try { controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
} }
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes) private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
throws IOException { throws IOException {
try { if (enable) {
if (enable) { Collection<String> conf = new ArrayList<>();
Collection<String> conf = new ArrayList<>(); conf.add("UseBridges 1");
conf.add("UseBridges 1"); File obfs4File = getObfs4ExecutableFile();
File obfs4File = getObfs4ExecutableFile(); if (bridgeTypes.contains(MEEK)) {
if (bridgeTypes.contains(MEEK)) { conf.add("ClientTransportPlugin meek_lite exec " +
conf.add("ClientTransportPlugin meek_lite exec " + obfs4File.getAbsolutePath());
obfs4File.getAbsolutePath());
}
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider.getBridges(bridgeType));
}
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} }
} catch (TorNotRunningException e) { if (bridgeTypes.contains(DEFAULT_OBFS4) ||
throw new RuntimeException(e); bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider.getBridges(bridgeType));
}
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} }
} }
@@ -593,8 +577,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("DisableNetwork", "1"); controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM"); controlConnection.shutdownTor("TERM");
controlSocket.close(); controlSocket.close();
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -726,11 +708,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}); });
Map<Integer, String> portLines = Map<Integer, String> portLines =
singletonMap(80, "127.0.0.1:" + port); singletonMap(80, "127.0.0.1:" + port);
try { controlConnection.addOnion(blob, portLines);
controlConnection.addOnion(blob, portLines);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
return new RendezvousEndpoint() { return new RendezvousEndpoint() {
@Override @Override
@@ -740,11 +718,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { controlConnection.delOnion(localOnion);
controlConnection.delOnion(localOnion);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
} }
}; };
@@ -772,8 +746,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void orConnStatus(String status, String orName) { public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status); if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CONNECTED")) state.onOrConnectionConnected(); //noinspection IfCanBeSwitch
if (status.equals("LAUNCHED")) state.onOrConnectionLaunched();
else if (status.equals("FAILED")) state.onOrConnectionFailed();
else if (status.equals("CONNECTED")) state.onOrConnectionConnected();
else if (status.equals("CLOSED")) state.onOrConnectionClosed(); else if (status.equals("CLOSED")) state.onOrConnectionClosed();
if ((status.equals("FAILED") || status.equals("CLOSED")) &&
state.getNumOrConnections() == 0) {
// Check whether we've lost connectivity
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
} }
@Override @Override
@@ -787,6 +771,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void message(String severity, String msg) { public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (msg.startsWith("Switching to guard context")) {
state.onSwitchingGuardContext();
}
} }
@Override @Override
@@ -868,6 +855,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (s.getNamespace().equals(ID.getString())) { if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated"); LOG.info("Tor settings updated");
settings = s.getSettings(); settings = s.getSettings();
// Works around a bug introduced in Tor 0.3.4.8.
// https://trac.torproject.org/projects/tor/ticket/28027
// Could be replaced with callback.transportDisabled()
// when fixed.
disableNetwork();
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
} }
@@ -880,6 +872,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void disableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) {
logException(LOG, WARNING, ex);
}
});
}
private void updateConnectionStatus(NetworkStatus status, private void updateConnectionStatus(NetworkStatus status,
boolean charging) { boolean charging) {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
@@ -976,20 +978,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
try { controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
} }
private void useIpv6(boolean ipv6Only) throws IOException { private void useIpv6(boolean ipv6Only) throws IOException {
try { controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1"); controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
} }
@ThreadSafe @ThreadSafe
@@ -1013,14 +1007,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
@GuardedBy("this") @GuardedBy("this")
private int orConnectionsConnected = 0; private int orConnectionsPending = 0, orConnectionsConnected = 0;
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; started = true;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() { private synchronized boolean isTorRunning() {
return started && !stopped; return started && !stopped;
} }
@@ -1078,38 +1071,63 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (reasonsDisabled != 0) return DISABLED; if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING; if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE; if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0 return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
? ACTIVE : ENABLING;
} }
private synchronized int getReasonsDisabled() { private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0; return getState() == DISABLED ? reasonsDisabled : 0;
} }
private synchronized void onOrConnectionLaunched() {
orConnectionsPending++;
logOrConnections();
}
private synchronized void onOrConnectionFailed() {
orConnectionsPending--;
if (orConnectionsPending < 0) {
LOG.warning("Count was zero before connection failed");
orConnectionsPending = 0;
}
logOrConnections();
}
private synchronized void onOrConnectionConnected() { private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected; orConnectionsPending--;
if (orConnectionsPending < 0) {
LOG.warning("Count was zero before connection connected");
orConnectionsPending = 0;
}
orConnectionsConnected++; orConnectionsConnected++;
logOrConnections(); logOrConnections();
if (oldConnected == 0) callback.pluginStateChanged(getState());
} }
private synchronized void onOrConnectionClosed() { private synchronized void onOrConnectionClosed() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected--; orConnectionsConnected--;
if (orConnectionsConnected < 0) { if (orConnectionsConnected < 0) {
LOG.warning("Count was zero before connection closed"); LOG.warning("Count was zero before connection closed");
orConnectionsConnected = 0; orConnectionsConnected = 0;
} }
logOrConnections(); logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) { }
callback.pluginStateChanged(getState());
} private synchronized void onSwitchingGuardContext() {
// Tor doesn't seem to report events for connections belonging to
// the old guard context, so we have to reset the counters
orConnectionsPending = 0;
orConnectionsConnected = 0;
logOrConnections();
}
private synchronized int getNumOrConnections() {
return orConnectionsPending + orConnectionsConnected;
} }
@GuardedBy("this") @GuardedBy("this")
private void logOrConnections() { private void logOrConnections() {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected"); LOG.info("OR connections: " + orConnectionsPending
+ " pending, " + orConnectionsConnected + " connected");
} }
} }
} }

View File

@@ -1,134 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@Immutable
@NotNullByDefault
abstract class TorPluginFactory implements DuplexPluginFactory {
protected static final Logger LOG =
getLogger(TorPluginFactory.class.getName());
protected static final int MAX_LATENCY = 30 * 1000; // 30 seconds
protected 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;
protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
protected final Clock clock;
protected final CryptoComponent crypto;
protected final File torDirectory;
protected final int torSocksPort;
protected final int torControlPort;
TorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
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.clock = clock;
this.crypto = crypto;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
}
@Nullable
abstract String getArchitectureForTorBinary();
abstract TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture);
@Override
public TransportId getId() {
return TorConstants.ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = getArchitectureForTorBinary();
if (architecture == null) {
LOG.warning("Tor is not supported on this architecture");
return null;
}
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
TorPlugin plugin = createPluginInstance(backoff, torRendezvousCrypto,
callback, architecture);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -19,8 +19,6 @@ class RecordWriterImpl implements RecordWriter {
private final OutputStream out; private final OutputStream out;
private final byte[] header = new byte[RECORD_HEADER_BYTES]; private final byte[] header = new byte[RECORD_HEADER_BYTES];
private long bytesWritten = 0;
RecordWriterImpl(OutputStream out) { RecordWriterImpl(OutputStream out) {
this.out = out; this.out = out;
} }
@@ -33,7 +31,6 @@ class RecordWriterImpl implements RecordWriter {
ByteUtils.writeUint16(payload.length, header, 2); ByteUtils.writeUint16(payload.length, header, 2);
out.write(header); out.write(header);
out.write(payload); out.write(payload);
bytesWritten += RECORD_HEADER_BYTES + payload.length;
} }
@Override @Override
@@ -45,9 +42,4 @@ class RecordWriterImpl implements RecordWriter {
public void close() throws IOException { public void close() throws IOException {
out.close(); out.close();
} }
@Override
public long getBytesWritten() {
return bytesWritten;
}
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.rendezvous; package org.briarproject.bramble.rendezvous;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
@@ -43,6 +42,7 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
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.security.GeneralSecurityException; import java.security.GeneralSecurityException;

View File

@@ -10,7 +10,6 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT; import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_CONNECT_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT; import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
@Module @Module
@@ -21,6 +20,6 @@ public class SocksModule {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1", InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
torSocksPort); torSocksPort);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT, return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
EXTRA_CONNECT_TIMEOUT, EXTRA_SOCKET_TIMEOUT); EXTRA_SOCKET_TIMEOUT);
} }
} }

View File

@@ -26,18 +26,15 @@ class SocksSocket extends Socket {
"Address type not supported" "Address type not supported"
}; };
@SuppressWarnings("MismatchedReadAndWriteOfArray")
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4]; private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private final SocketAddress proxy; private final SocketAddress proxy;
private final int connectToProxyTimeout; private final int connectToProxyTimeout, extraSocketTimeout;
private final int extraConnectTimeout, extraSocketTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout, SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
int extraConnectTimeout, int extraSocketTimeout) { int extraSocketTimeout) {
this.proxy = proxy; this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout; this.connectToProxyTimeout = connectToProxyTimeout;
this.extraConnectTimeout = extraConnectTimeout;
this.extraSocketTimeout = extraSocketTimeout; this.extraSocketTimeout = extraSocketTimeout;
} }
@@ -69,7 +66,7 @@ class SocksSocket extends Socket {
// Use the supplied timeout temporarily, plus any configured extra // Use the supplied timeout temporarily, plus any configured extra
int oldTimeout = getSoTimeout(); int oldTimeout = getSoTimeout();
setSoTimeout(timeout + extraConnectTimeout); setSoTimeout(timeout + extraSocketTimeout);
// Connect to the endpoint via the proxy // Connect to the endpoint via the proxy
sendConnectRequest(out, host, port); sendConnectRequest(out, host, port);

View File

@@ -11,21 +11,18 @@ import javax.net.SocketFactory;
class SocksSocketFactory extends SocketFactory { class SocksSocketFactory extends SocketFactory {
private final SocketAddress proxy; private final SocketAddress proxy;
private final int connectToProxyTimeout; private final int connectToProxyTimeout, extraSocketTimeout;
private final int extraConnectTimeout, extraSocketTimeout;
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout, SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
int extraConnectTimeout, int extraSocketTimeout) { int extraSocketTimeout) {
this.proxy = proxy; this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout; this.connectToProxyTimeout = connectToProxyTimeout;
this.extraConnectTimeout = extraConnectTimeout;
this.extraSocketTimeout = extraSocketTimeout; this.extraSocketTimeout = extraSocketTimeout;
} }
@Override @Override
public Socket createSocket() { public Socket createSocket() {
return new SocksSocket(proxy, connectToProxyTimeout, return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
extraConnectTimeout, extraSocketTimeout);
} }
@Override @Override

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