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
261 changed files with 3345 additions and 9430 deletions

View File

@@ -118,3 +118,11 @@ mailbox integration test:
- cd "$CI_PROJECT_DIR"
- bramble-core/src/test/bash/wait-for-mailbox.sh
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
pre_release_tests:
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 {
minSdkVersion 16
targetSdkVersion 30
versionCode 10408
versionName "1.4.8"
versionCode 10406
versionName "1.4.6"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

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

View File

@@ -86,8 +86,8 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
BluetoothConnectionFactory<BluetoothSocket> connectionFactory =
new AndroidBluetoothConnectionFactory(connectionLimiter,
wakeLockManager, timeoutMonitor);
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, connectionFactory, ioExecutor,
wakefulIoExecutor, secureRandom, androidExecutor, app,

View File

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

View File

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

View File

@@ -61,8 +61,8 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
wakefulIoExecutor, app, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME, CONNECTION_TIMEOUT);

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
@@ -63,6 +64,7 @@ class AndroidTorPlugin extends TorPlugin {
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -73,7 +75,7 @@ class AndroidTorPlugin extends TorPlugin {
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, torDirectory, torSocksPort, torControlPort);
this.app = app;

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.provider.Settings;
import org.briarproject.bramble.api.Pair;
@@ -135,8 +134,4 @@ public class AndroidUtils {
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.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -20,9 +19,10 @@ import org.briarproject.bramble.api.sync.MessageId;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@NotNullByDefault
public interface ClientHelper {
@@ -127,17 +127,16 @@ public interface ClientHelper {
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
* @throws FormatException if the message elements are invalid
* @return the properties for using the Mailbox, or null if there is no
* Mailbox available
* @throws FormatException if the properties are not valid
*/
MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException;
List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException;
@Nullable
MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact

View File

@@ -16,17 +16,6 @@ public interface ConnectionManager {
*/
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
/**
* Manages an incoming connection from a contact via a mailbox.
* <p>
* This method does not mark the tag as recognised until after the data
* has been read from the {@link TransportConnectionReader}, at which
* point the {@link TagController} is called to decide whether the tag
* should be marked as recognised.
*/
void manageIncomingConnection(TransportId t, TransportConnectionReader r,
TagController c);
/**
* Manages an incoming connection from a contact over a duplex transport.
*/
@@ -57,21 +46,4 @@ public interface ConnectionManager {
*/
void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d);
/**
* An interface for controlling whether a tag should be marked as
* recognised.
*/
interface TagController {
/**
* This method is only called if a tag was read from the corresponding
* {@link TransportConnectionReader} and recognised.
*
* @param exception True if an exception was thrown while reading from
* the {@link TransportConnectionReader}, after successfully reading
* and recognising the tag.
* @return True if the tag should be marked as recognised.
*/
boolean shouldMarkTagAsRecognised(boolean exception);
}
}

View File

@@ -33,18 +33,11 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* Encapsulates the database implementation and exposes high-level operations
* to other components.
* <p>
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
* {@link #close()} methods, which must not be called concurrently, the
* database can be accessed from any thread. See {@link TransactionManager}
* for locking behaviour.
*/
@ThreadSafe
@NotNullByDefault
public interface DatabaseComponent extends TransactionManager {
@@ -200,15 +193,26 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException;
/**
* Returns a batch of messages for the given contact, for transmission over
* a transport with the given maximum latency. The total length of the
* messages, including record headers, will be no more than the given
* capacity. Returns null if there are no sendable messages that would fit
* in the given capacity.
* Returns a batch of messages for the given contact, with a total length
* less than or equal to the given length, for transmission over a
* transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length.
*/
@Nullable
Collection<Message> generateBatch(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException;
int maxLength, long maxLatency) throws DbException;
/**
* Returns a batch of messages for the given contact containing the
* messages with the given IDs, for transmission over a transport with
* the given maximum latency.
* <p/>
* If any of the given messages are not in the database or are not visible
* to the contact, they are omitted from the batch without throwing an
* exception.
*/
Collection<Message> generateBatch(Transaction txn, ContactId c,
Collection<MessageId> ids, long maxLatency) throws DbException;
/**
* Returns an offer for the given contact for transmission over a
@@ -228,16 +232,15 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException;
/**
* Returns a batch of messages for the given contact, for transmission over
* a transport with the given maximum latency. Only messages that have been
* requested by the contact are returned. The total length of the messages,
* including record headers, will be no more than the given capacity.
* Returns null if there are no sendable messages that have been requested
* by the contact and would fit in the given capacity.
* Returns a batch of messages for the given contact, with a total length
* less than or equal to the given length, for transmission over a
* transport with the given maximum latency. Only messages that have been
* requested by the contact are returned. Returns null if there are no
* sendable messages that fit in the given length.
*/
@Nullable
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException;
int maxLength, long maxLatency) throws DbException;
/**
* Returns the contact with the given ID.
@@ -341,30 +344,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
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.
* <p/>
@@ -481,30 +460,15 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException;
/**
* Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact.
*
* @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling
* {@link #setMessagesSent(Transaction, ContactId, Collection, long)}.
*/
@Nullable
Message getMessageToSend(Transaction txn, ContactId c, MessageId m,
long maxLatency, boolean markAsSent) throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact.
* <p>
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, long)}
* this method may return messages that have already been sent and are
* not yet due for retransmission.
* given contact, together with their raw lengths. This may include
* messages that have already been sent and are not yet due for
* retransmission.
* <p/>
* Read-only.
*/
Collection<MessageId> getUnackedMessagesToSend(Transaction txn,
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
@@ -684,13 +648,6 @@ public interface DatabaseComponent extends TransactionManager {
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException;
/**
* Records an ack for the given messages as having been sent to the given
* contact.
*/
void setAckSent(Transaction txn, ContactId c, Collection<MessageId> acked)
throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
@@ -737,13 +694,6 @@ public interface DatabaseComponent extends TransactionManager {
void setMessageState(Transaction txn, MessageId m, MessageState state)
throws DbException;
/**
* Records the given messages as having been sent to the given contact
* over a transport with the given maximum latency.
*/
void setMessagesSent(Transaction txn, ContactId c,
Collection<MessageId> sent, long maxLatency) throws DbException;
/**
* Adds dependencies for a message
*/
@@ -796,4 +746,6 @@ public interface DatabaseComponent extends TransactionManager {
*/
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
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
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
* discarded.
* <p>
* It is not mandatory to use this executor for database tasks. The database
* can be accessed from any thread, but this executor's guarantee that tasks
* are run in the order they're submitted may be useful in some cases.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})

View File

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

View File

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

View File

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

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.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import javax.annotation.Nullable;
@@ -43,14 +41,4 @@ public interface MailboxManager {
*/
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 javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@@ -15,36 +14,13 @@ public class MailboxProperties {
private final MailboxAuthToken authToken;
private final boolean owner;
private final List<MailboxVersion> serverSupports;
@Nullable
private final MailboxFolderId inboxId; // Null for own mailbox
@Nullable
private final MailboxFolderId outboxId; // Null for own mailbox
/**
* Constructor for properties used by the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) {
boolean owner, List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = true;
this.owner = owner;
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() {
@@ -67,14 +43,4 @@ public class MailboxProperties {
public List<MailboxVersion> getServerSupports() {
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

@@ -9,31 +9,31 @@ import org.briarproject.bramble.api.sync.ClientId;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxUpdateManager {
public interface MailboxPropertyManager {
/**
* The unique ID of the mailbox update (properties) client.
* 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 update (properties) client.
* The current major version of the mailbox property client.
*/
int MAJOR_VERSION = 2;
int MAJOR_VERSION = 0;
/**
* The current minor version of the mailbox update (properties) client.
* The current minor version of the mailbox property client.
*/
int MINOR_VERSION = 0;
/**
* The number of properties required for an update message with a mailbox.
* The number of properties required for a (non-empty) update message.
*/
int PROP_COUNT = 4;
/**
* The required properties of an update message with a mailbox.
* The required properties of a non-empty update message.
*/
String PROP_KEY_ONION = "onion";
String PROP_KEY_AUTHTOKEN = "authToken";
@@ -57,16 +57,11 @@ public interface MailboxUpdateManager {
*/
String MSG_KEY_LOCAL = "local";
/**
* Key in the client's local group for storing the clientSupports list that
* was last sent out.
*/
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
@Nullable
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
throws DbException;
@Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
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.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.Nullable;
@NotNullByDefault
@@ -28,8 +26,6 @@ public interface MailboxSettingsManager {
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException;
void removeOwnMailboxProperties(Transaction txn) throws DbException;
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now)
@@ -51,8 +47,7 @@ public interface MailboxSettingsManager {
* @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars)
*/
void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports)
void mailboxPaired(Transaction txn, String ownOnion)
throws DbException;
/**

View File

@@ -2,30 +2,20 @@ package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
@Immutable
@NotNullByDefault
public class MailboxStatus {
private final long lastAttempt, lastSuccess;
private final int attemptsSinceSuccess;
private final List<MailboxVersion> serverSupports;
public MailboxStatus(long lastAttempt, long lastSuccess,
int attemptsSinceSuccess,
List<MailboxVersion> serverSupports) {
int attemptsSinceSuccess) {
this.lastAttempt = lastAttempt;
this.lastSuccess = lastSuccess;
this.attemptsSinceSuccess = attemptsSinceSuccess;
this.serverSupports = serverSupports;
}
/**
@@ -66,21 +56,4 @@ public class MailboxStatus {
public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess;
}
/**
* @return true if this status indicates a problem with the mailbox.
*/
public boolean hasProblem(long now) {
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
}
/**
* @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,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.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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,9 +1,7 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.event.EventBus;
public interface BackoffFactory {
Backoff createBackoff(EventBus eventBus, TransportId transportId,
int minInterval, int maxInterval, double base);
Backoff createBackoff(int minInterval, int maxInterval,
double base);
}

View File

@@ -56,9 +56,4 @@ public interface PluginCallback extends ConnectionHandler {
* This method can safely be called while holding a lock.
*/
void pluginStateChanged(State state);
/**
* Informs the callback that the plugin's polling interval has decreased.
*/
void pollingIntervalDecreased();
}

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.SECONDS;
public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
@@ -12,9 +10,8 @@ public interface TorConstants {
int DEFAULT_SOCKS_PORT = 59050;
int DEFAULT_CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
// Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2";

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a plugin's polling interval decreases.
*/
@Immutable
@NotNullByDefault
public class PollingIntervalDecreasedEvent extends Event {
private final TransportId transportId;
public PollingIntervalDecreasedEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}

View File

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

View File

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

View File

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

View File

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

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.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.ClientId;
@@ -40,7 +36,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -51,7 +46,6 @@ import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
@@ -228,19 +222,6 @@ public class TestUtils {
getAgreementPublicKey(), verified);
}
public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) {
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) {
return new MailboxProperties(baseUrl, authToken, serverSupports);
}
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId);
}
public static void writeBytes(File file, byte[] bytes)
throws IOException {
FileOutputStream outputStream = new FileOutputStream(file);
@@ -293,30 +274,26 @@ public class TestUtils {
return Math.sqrt(getVariance(samples));
}
public static boolean isOptionalTestEnabled(Class<?> testClass) {
public static boolean isOptionalTestEnabled(Class testClass) {
String optionalTests = System.getenv("OPTIONAL_TESTS");
return optionalTests != null &&
asList(optionalTests.split(",")).contains(testClass.getName());
}
public static boolean mailboxUpdateEqual(@Nullable MailboxUpdate a,
@Nullable MailboxUpdate b) {
public static boolean mailboxPropertiesUpdateEqual(
@Nullable MailboxPropertiesUpdate a,
@Nullable MailboxPropertiesUpdate b) {
if (a == null || b == null) {
return a == b;
}
if (!a.hasMailbox() && !b.hasMailbox()) {
return a.getClientSupports().equals(b.getClientSupports());
} else if (a.hasMailbox() && b.hasMailbox()) {
MailboxUpdateWithMailbox am = (MailboxUpdateWithMailbox) a;
MailboxUpdateWithMailbox bm = (MailboxUpdateWithMailbox) b;
return am.getClientSupports().equals(bm.getClientSupports()) &&
mailboxPropertiesEqual(am.getMailboxProperties(),
bm.getMailboxProperties());
}
return false;
return a.getOnion().equals(b.getOnion()) &&
a.getAuthToken().equals(b.getAuthToken()) &&
a.getInboxId().equals(b.getInboxId()) &&
a.getOutboxId().equals(b.getOutboxId());
}
public static boolean mailboxPropertiesEqual(@Nullable MailboxProperties a,
public static boolean mailboxPropertiesEqual(
@Nullable MailboxProperties a,
@Nullable MailboxProperties b) {
if (a == null || b == null) {
return a == b;
@@ -337,13 +314,4 @@ public class TestUtils {
}
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 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.4'
implementation 'org.briarproject:jtorctl:0.3'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

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.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -42,13 +39,12 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
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.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_ONION_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
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_PROPERTY_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@@ -416,28 +412,11 @@ class ClientHelperImpl implements ClientHelper {
}
@Override
public MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException {
List<MailboxVersion> clientSupportsList =
parseMailboxVersionList(clientSupports);
List<MailboxVersion> serverSupportsList =
parseMailboxVersionList(serverSupports);
// We must always learn what Mailbox API version(s) the client supports
if (clientSupports.isEmpty()) {
throw new FormatException();
}
@Nullable
public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
BdfDictionary properties) throws FormatException {
if (properties.isEmpty()) {
// No mailbox -- cannot claim to support any API versions!
if (!serverSupports.isEmpty()) {
throw new FormatException();
}
return new MailboxUpdate(clientSupportsList);
}
// Mailbox must be accompanied by the Mailbox API version(s) it supports
if (serverSupports.isEmpty()) {
throw new FormatException();
return null;
}
// Accepting more props than we need, for forward compatibility
if (properties.size() < PROP_COUNT) {
@@ -456,26 +435,9 @@ class ClientHelperImpl implements ClientHelper {
checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH);
String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props);
}
@Override
public List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException {
List<MailboxVersion> list = new ArrayList<>();
for (int i = 0; i < bdfList.size(); i++) {
BdfList element = bdfList.getList(i);
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
}
return list;
return new MailboxPropertiesUpdate(onion,
new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
new MailboxFolderId(outboxId));
}
@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];
read(in, tag);
return tag;

View File

@@ -67,15 +67,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionReader r) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, t, r, null));
}
@Override
public void manageIncomingConnection(TransportId t,
TransportConnectionReader r, TagController c) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, t, r, c));
syncSessionFactory, transportPropertyManager, t, r));
}
@Override

View File

@@ -1,9 +1,7 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -17,8 +15,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -27,8 +23,6 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
private final TransportId transportId;
private final TransportConnectionReader reader;
@Nullable
private final TagController tagController;
IncomingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
@@ -36,50 +30,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
TransportId transportId,
TransportConnectionReader reader,
@Nullable TagController tagController) {
TransportId transportId, TransportConnectionReader reader) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.transportId = transportId;
this.reader = reader;
this.tagController = tagController;
}
@Override
public void run() {
// Read and recognise the tag
byte[] tag;
StreamContext ctx;
try {
tag = readTag(reader.getInputStream());
// If we have a tag controller, defer marking the tag as recognised
if (tagController == null) {
ctx = keyManager.getStreamContext(transportId, tag);
} else {
ctx = keyManager.getStreamContextOnly(transportId, tag);
}
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError();
return;
}
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
LOG.info("Unrecognised tag");
onError();
onError(false);
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Received rendezvous stream, expected contact");
onError(tag);
onError(true);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onError(tag);
onError(true);
return;
}
try {
@@ -88,33 +65,15 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
LOG.info("Ignoring priority for simplex connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
// Success
markTagAsRecognisedIfRequired(false, tag);
reader.dispose(false, true);
} catch (IOException e) {
logException(LOG, WARNING, e);
onError(tag);
onError(true);
}
}
private void onError() {
disposeOnError(reader, false);
}
private void onError(byte[] tag) {
markTagAsRecognisedIfRequired(true, tag);
disposeOnError(reader, true);
}
private void markTagAsRecognisedIfRequired(boolean exception, byte[] tag) {
if (tagController != null &&
tagController.shouldMarkTagAsRecognised(exception)) {
try {
keyManager.markTagAsRecognised(transportId, tag);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private void onError(boolean recognised) {
disposeOnError(reader, recognised);
}
}

View File

@@ -406,12 +406,6 @@ interface Database<T> {
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
throws DbException;
/**
* Returns the length of the given message in bytes, including the
* message header.
*/
int getMessageLength(T txn, MessageId m) throws DbException;
/**
* Returns the metadata for all delivered messages in the given group.
* <p/>
@@ -502,8 +496,7 @@ interface Database<T> {
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact. The total length of the messages including record headers
* will be no more than the given capacity.
* given contact, up to the given total length.
* <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are
@@ -511,20 +504,20 @@ interface Database<T> {
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity,
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
long maxLatency) throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact.
* given contact, together with their raw lengths.
* <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, long, long)} this
* Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
* method may return messages that have already been sent and are not yet
* due for retransmission.
* <p/>
* Read-only.
*/
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c)
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException;
/**
@@ -605,14 +598,13 @@ interface Database<T> {
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact and have been requested by the contact. The total length
* of the messages including record headers will be no more than the given
* capacity.
* given contact and have been requested by the contact, up to the given
* total length.
* <p/>
* Read-only.
*/
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
long capacity, long maxLatency) throws DbException;
int maxLength, long maxLatency) throws DbException;
/**
* Returns all settings in the given namespace.
@@ -870,4 +862,6 @@ interface Database<T> {
* Stores the given transport keys, deleting any keys they have replaced.
*/
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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -86,7 +87,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -424,14 +424,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException {
ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getMessagesToSend(txn, c, capacity, maxLatency);
if (ids.isEmpty()) return null;
db.getMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
@@ -440,11 +439,38 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
messages.add(message);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
}
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, Collection<MessageId> ids, long maxLatency)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
List<MessageId> sentIds = new ArrayList<>(ids.size());
for (MessageId m : ids) {
if (db.containsVisibleMessage(txn, c, m)) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
sentIds.add(m);
db.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
@Override
public Offer generateOffer(Transaction transaction, ContactId c,
@@ -479,14 +505,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Collection<Message> generateRequestedBatch(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException {
ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency);
if (ids.isEmpty()) return null;
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
@@ -495,6 +520,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
messages.add(message);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
@@ -609,24 +635,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
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
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException {
@@ -732,29 +740,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status;
}
@Nullable
@Override
public Message getMessageToSend(Transaction transaction, ContactId c,
MessageId m, long maxLatency, boolean markAsSent)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
if (!db.containsVisibleMessage(txn, c, m)) return null;
Message message = db.getMessage(txn, m);
if (markAsSent) {
db.updateRetransmissionData(txn, c, m, maxLatency);
db.lowerRequestedFlag(txn, c, singletonList(m));
transaction.attach(new MessagesSentEvent(c, singletonList(m),
message.getRawLength()));
}
return message;
}
@Override
public Collection<MessageId> getUnackedMessagesToSend(
Transaction transaction, ContactId c) throws DbException {
public Map<MessageId, Integer> getUnackedMessagesToSend(
Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
@@ -1080,20 +1069,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeTransportKeys(txn, t, k);
}
@Override
public void setAckSent(Transaction transaction, ContactId c,
Collection<MessageId> acked) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
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
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException {
@@ -1140,7 +1115,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c);
List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}
@@ -1188,28 +1163,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageStateChangedEvent(m, false, state));
}
@Override
public void setMessagesSent(Transaction transaction, ContactId c,
Collection<MessageId> sent, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<MessageId> visible = new ArrayList<>(sent.size());
for (MessageId m : sent) {
if (db.containsVisibleMessage(txn, c, m)) {
visible.add(m);
totalLength += db.getMessageLength(txn, m);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
}
db.lowerRequestedFlag(txn, c, visible);
if (!visible.isEmpty()) {
transaction.attach(new MessagesSentEvent(c, visible, totalLength));
}
}
@Override
public void addMessageDependencies(Transaction transaction,
Message dependent, Collection<MessageId> dependencies)
@@ -1303,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 {
@Override

View File

@@ -13,8 +13,12 @@ import org.briarproject.bramble.util.StringUtils;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
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 org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
import static org.briarproject.bramble.util.LogUtils.logException;
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
protected Connection createConnection() throws DbException, SQLException {
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
protected Connection createConnection() throws DbException, SQLException {
SecretKey key = this.key;

View File

@@ -51,6 +51,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -69,14 +70,12 @@ import static java.sql.Types.BOOLEAN;
import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -103,11 +102,6 @@ abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
static final int CODE_SCHEMA_VERSION = 50;
/**
* The maximum number of idle connections to keep open.
*/
private static final int MAX_CONNECTION_POOL_SIZE = 1;
// Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
private static final int OFFSET_CURR = 0;
@@ -369,7 +363,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private final Condition connectionsChanged = connectionsLock.newCondition();
@GuardedBy("connectionsLock")
private final LinkedList<Connection> connectionPool = new LinkedList<>();
private final LinkedList<Connection> connections = new LinkedList<>();
@GuardedBy("connectionsLock")
private int openConnections = 0;
@@ -577,8 +571,7 @@ abstract class JdbcDatabase implements Database<Connection> {
connectionsLock.lock();
try {
if (closed) throw new DbClosedException();
txn = connectionPool.poll();
logConnectionCounts();
txn = connections.poll();
} finally {
connectionsLock.unlock();
}
@@ -589,14 +582,7 @@ abstract class JdbcDatabase implements Database<Connection> {
txn.setAutoCommit(false);
connectionsLock.lock();
try {
// The DB may have been closed since the check above
if (closed) {
tryToClose(txn, LOG, WARNING);
throw new DbClosedException();
}
openConnections++;
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
@@ -607,91 +593,67 @@ abstract class JdbcDatabase implements Database<Connection> {
return txn;
}
@GuardedBy("connectionsLock")
private void logConnectionCounts() {
if (LOG.isLoggable(FINE)) {
LOG.fine(openConnections + " connections open, "
+ connectionPool.size() + " in pool");
}
}
@Override
public void abortTransaction(Connection txn) {
// The transaction may have been aborted due to an earlier exception,
// so close the connection rather than returning it to the pool
try {
txn.rollback();
connectionsLock.lock();
try {
connections.add(txn);
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
} catch (SQLException e) {
// Try to close the connection
logException(LOG, WARNING, e);
}
closeConnection(txn);
}
private void closeConnection(Connection txn) {
tryToClose(txn, LOG, WARNING);
connectionsLock.lock();
try {
openConnections--;
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
tryToClose(txn, LOG, WARNING);
// Whatever happens, allow the database to close
connectionsLock.lock();
try {
openConnections--;
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
}
}
@Override
public void commitTransaction(Connection txn) throws DbException {
// If the transaction commits successfully then return the connection
// to the pool, otherwise close it
try {
txn.commit();
returnConnectionToPool(txn);
} catch (SQLException e) {
logException(LOG, WARNING, e);
closeConnection(txn);
throw new DbException(e);
}
}
private void returnConnectionToPool(Connection txn) {
boolean shouldClose;
connectionsLock.lock();
try {
shouldClose = connectionPool.size() >= MAX_CONNECTION_POOL_SIZE;
if (shouldClose) openConnections--;
else connectionPool.add(txn);
logConnectionCounts();
connections.add(txn);
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
if (shouldClose) tryToClose(txn, LOG, WARNING);
}
void closeAllConnections() {
void closeAllConnections() throws SQLException {
boolean interrupted = false;
connectionsLock.lock();
try {
closed = true;
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
openConnections -= connectionPool.size();
connectionPool.clear();
for (Connection c : connections) c.close();
openConnections -= connections.size();
connections.clear();
while (openConnections > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Waiting for " + openConnections
+ " connections to be closed");
}
try {
connectionsChanged.await();
} catch (InterruptedException e) {
LOG.warning("Interrupted while closing connections");
interrupted = true;
}
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
openConnections -= connectionPool.size();
connectionPool.clear();
for (Connection c : connections) c.close();
openConnections -= connections.size();
connections.clear();
}
LOG.info("All connections closed");
} finally {
connectionsLock.unlock();
}
@@ -1917,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
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
GroupId g) throws DbException {
@@ -2290,8 +2227,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public Collection<MessageId> getMessagesToSend(Connection txn,
ContactId c, long capacity, long maxLatency) throws DbException {
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
@@ -2311,11 +2248,12 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setLong(4, maxLatency);
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) {
int length = rs.getInt(1);
if (capacity < RECORD_HEADER_BYTES + length) break;
if (total + length > maxLength) break;
ids.add(new MessageId(rs.getBytes(2)));
capacity -= RECORD_HEADER_BYTES + length;
total += length;
}
rs.close();
ps.close();
@@ -2328,12 +2266,12 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public Collection<MessageId> getUnackedMessagesToSend(Connection txn,
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId FROM statuses"
String sql = "SELECT length, messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
@@ -2342,11 +2280,15 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
Map<MessageId, Integer> results = new LinkedHashMap<>();
while (rs.next()) {
int length = rs.getInt(1);
MessageId id = new MessageId(rs.getBytes(2));
results.put(id, length);
}
rs.close();
ps.close();
return ids;
return results;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
@@ -2459,7 +2401,6 @@ abstract class JdbcDatabase implements Database<Connection> {
MessageId m = new MessageId(rs.getBytes(1));
GroupId g = new GroupId(rs.getBytes(2));
Collection<MessageId> messageIds = ids.get(g);
//noinspection Java8MapApi
if (messageIds == null) {
messageIds = new ArrayList<>();
ids.put(g, messageIds);
@@ -2586,7 +2527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
ContactId c, long capacity, long maxLatency) throws DbException {
ContactId c, int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
@@ -2606,11 +2547,12 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setLong(4, maxLatency);
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) {
int length = rs.getInt(1);
if (capacity < RECORD_HEADER_BYTES + length) break;
if (total + length > maxLength) break;
ids.add(new MessageId(rs.getBytes(2)));
capacity -= RECORD_HEADER_BYTES + length;
total += length;
}
rs.close();
ps.close();
@@ -2764,7 +2706,6 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c);
//noinspection Java8MapApi
if (transportIds == null) {
transportIds = new ArrayList<>();
ids.put(c, transportIds);

View File

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

View File

@@ -190,10 +190,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
return;
}
try {
if (state == STOPPING) {
LOG.info("Already stopped");
return;
}
LOG.info("Stopping services");
state = 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,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,241 +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) throw new IllegalStateException();
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;
addObserver = true;
} else if (state == State.DOWNLOAD_CYCLE_2) {
LOG.info("Second download cycle finished");
state = State.FINISHED;
}
}
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.MailboxFolderId;
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.IOException;
@@ -18,12 +16,8 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
@NotNullByDefault
interface MailboxApi {
List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException;
/**
* 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;
}
@Override
public List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/versions");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
return parseServerSupports(node);
} catch (JacksonException e) {
throw new ApiException();
}
}
@Override
public MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException {
@@ -93,40 +76,36 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) {
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(),
MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node));
true, serverSupports);
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
private List<MailboxVersion> parseServerSupports(JsonNode node)
throws ApiException {
List<MailboxVersion> serverSupports = new ArrayList<>();
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
for (JsonNode versionNode : serverSupportsNode) {
if (!versionNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) versionNode;
JsonNode majorNode = objectNode.get("major");
JsonNode minorNode = objectNode.get("minor");
if (majorNode == null || !majorNode.isNumber()) {
throw new ApiException();
}
if (minorNode == null || !minorNode.isNumber()) {
throw new ApiException();
}
int major = majorNode.asInt();
int minor = minorNode.asInt();
if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor));
}
return serverSupports;
}
@Override
public boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/status");
if (response.code() == 401) throw new ApiException();
return response.isSuccessful();

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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;
/**
* Handles a file that has been downloaded. The file should be created
* with {@link #createTempFileForDownload()}.
*/
void handleDownloadedFile(File f);
}

View File

@@ -1,174 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
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.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.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";
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 {
// Wait for orphaned files to be handled before creating new files
try {
orphanLatch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", downloadDir);
}
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) {
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
* handle any files that were left in the download directory at the last
* shutdown.
*/
@IoExecutor
private void handleOrphanedFiles() {
try {
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
File[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphans, new files can be created
orphanLatch.countDown();
if (orphans != null) for (File f : orphans) 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");
if (!file.delete()) {
LOG.warning("Failed to delete downloaded file");
}
}
}
}
}

View File

@@ -102,55 +102,23 @@ class MailboxManagerImpl implements MailboxManager {
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException();
success = api.checkStatus(props);
} catch (DbException e) {
logException(LOG, WARNING, e);
// we don't treat this is a failure to record
return false;
} catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure
} catch (DbException | IOException | MailboxApi.ApiException e) {
success = false;
logException(LOG, WARNING, e);
}
try {
recordCheckResult(success);
} catch (DbException e) {
logException(LOG, WARNING, e);
if (success) {
try {
// we are only recording successful connections here
// as those update the UI and failures might be false negatives
db.transaction(false, txn ->
mailboxSettingsManager.recordSuccessfulConnection(txn,
clock.currentTimeMillis()));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
return success;
}
private void recordCheckResult(boolean success) throws DbException {
long now = clock.currentTimeMillis();
db.transaction(false, txn -> {
if (success) {
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
} else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
}
});
}
@Override
public boolean unPair() throws DbException {
MailboxProperties properties = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (properties == null) {
// no more mailbox, that's strange but possible if called in quick
// succession, so let's return true this time
return true;
}
boolean wasWiped;
try {
api.wipeMailbox(properties);
wasWiped = true;
} catch (IOException | MailboxApi.ApiException e) {
logException(LOG, WARNING, e);
wasWiped = false;
}
db.transaction(false,
mailboxSettingsManager::removeOwnMailboxProperties);
return wasWiped;
}
}

View File

@@ -1,42 +1,34 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
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.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
@Module
public class MailboxModule {
public static class EagerSingletons {
@Inject
MailboxUpdateValidator mailboxUpdateValidator;
MailboxPropertyValidator mailboxPropertyValidator;
@Inject
MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxFileManager mailboxFileManager;
MailboxPropertyManager mailboxPropertyManager;
}
@Provides
@@ -64,54 +56,31 @@ public class MailboxModule {
@Provides
@Singleton
MailboxUpdateValidator provideMailboxUpdateValidator(
ValidationManager validationManager,
ClientHelper clientHelper,
MetadataEncoder metadataEncoder,
Clock clock,
FeatureFlags featureFlags) {
MailboxUpdateValidator validator = new MailboxUpdateValidator(
MailboxPropertyValidator provideMailboxPropertyValidator(
ValidationManager validationManager, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
MailboxPropertyValidator validator = new MailboxPropertyValidator(
clientHelper, metadataEncoder, clock);
if (featureFlags.shouldEnableMailbox()) {
validationManager.registerMessageValidator(CLIENT_ID,
MAJOR_VERSION, validator);
}
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
return validator;
}
@Provides
List<MailboxVersion> provideClientSupports() {
return CLIENT_SUPPORTS;
}
@Provides
@Singleton
MailboxUpdateManager provideMailboxUpdateManager(
FeatureFlags featureFlags,
MailboxPropertyManager provideMailboxPropertyManager(
LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManagerImpl mailboxUpdateManager) {
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
}
return mailboxUpdateManager;
}
@Provides
@Singleton
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
if (featureFlags.shouldEnableMailbox()) {
eventBus.addListener(mailboxFileManager);
}
return mailboxFileManager;
MailboxPropertyManagerImpl mailboxPropertyManager) {
lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
mailboxPropertyManager);
contactManager.registerContactHook(mailboxPropertyManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxPropertyManager);
mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager);
return mailboxPropertyManager;
}
}

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.event.EventExecutor;
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.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
@@ -24,7 +24,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxPropertyManager mailboxPropertyManager;
@Inject
MailboxPairingTaskFactoryImpl(
@@ -34,20 +34,20 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
MailboxPropertyManager mailboxPropertyManager) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxPropertyManager = mailboxPropertyManager;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
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.MailboxPairingTask;
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.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
@@ -51,7 +51,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxPropertyManager mailboxPropertyManager;
private final Object lock = new Object();
@GuardedBy("lock")
@@ -68,7 +68,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
MailboxPropertyManager mailboxPropertyManager) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
@@ -76,7 +76,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxPropertyManager = mailboxPropertyManager;
state = new MailboxPairingState.QrCodeReceived();
}
@@ -125,9 +125,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
// timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate update = mailboxUpdateManager.getRemoteUpdate(
txn, c.getId());
if (update == null || !update.hasMailbox()) {
MailboxPropertiesUpdate remoteProps = mailboxPropertyManager
.getRemoteProperties(txn, c.getId());
if (remoteProps == null) {
db.resetUnackedMessagesToSend(txn, c.getId());
}
}
@@ -177,10 +177,11 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
String baseUrl = "http://" + onion + ".onion";
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
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.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -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.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
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.ClientVersioningHook;
import java.util.List;
import java.util.Map;
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;
@NotNullByDefault
class MailboxUpdateManagerImpl implements MailboxUpdateManager,
class MailboxPropertyManagerImpl implements MailboxPropertyManager,
OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook, MailboxHook {
private final List<MailboxVersion> clientSupports;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final ClientVersioningManager clientVersioningManager;
@@ -64,14 +59,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
private final Group localGroup;
@Inject
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports,
DatabaseComponent db, ClientHelper clientHelper,
MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock,
MailboxSettingsManager mailboxSettingsManager,
CryptoComponent crypto) {
this.clientSupports = clientSupports;
this.db = db;
this.clientHelper = clientHelper;
this.clientVersioningManager = clientVersioningManager;
@@ -87,46 +80,12 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) {
try {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
txn, localGroup.getId());
BdfList sent = meta.getList(GROUP_KEY_SENT_CLIENT_SUPPORTS);
if (clientHelper.parseMailboxVersionList(sent)
.equals(clientSupports)) {
return;
}
} catch (FormatException e) {
throw new DbException();
}
// Our current clientSupports list has changed compared to what we
// last sent out.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate latest = getLocalUpdate(txn, c.getId());
MailboxUpdate updated;
if (latest.hasMailbox()) {
updated = new MailboxUpdateWithMailbox(
(MailboxUpdateWithMailbox) latest, clientSupports);
} else {
updated = new MailboxUpdate(clientSupports);
}
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), updated);
}
} else {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) {
addingContact(txn, c);
}
return;
}
try {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS,
encodeSupportsList(clientSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) {
addingContact(txn, c);
}
}
@@ -141,15 +100,11 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
// If we are paired, create and send props to the newly added contact
MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn);
if (ownProps != null) {
// We are paired, create and send props to the newly added contact
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getOnion());
} else {
// Not paired, but we still want to get our clientSupports sent
sendUpdateNoMailbox(txn, c);
createAndSendProperties(txn, c, ownProps.getOnion());
}
}
@@ -159,17 +114,17 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
}
@Override
public void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports) throws DbException {
public void mailboxPaired(Transaction txn, String ownOnion)
throws DbException {
for (Contact c : db.getContacts(txn)) {
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
createAndSendProperties(txn, c, ownOnion);
}
}
@Override
public void mailboxUnpaired(Transaction txn) throws DbException {
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());
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
MailboxUpdate u = parseUpdate(body);
txn.attach(new RemoteMailboxUpdateEvent(c, u));
MailboxPropertiesUpdate p = parseProperties(body);
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
// Reset message retransmission timers for the contact. Avoiding
// messages getting stranded:
// - on our mailbox, if they now have a mailbox but didn't before
@@ -216,82 +171,70 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
}
@Override
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException {
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true);
// An update (with or without mailbox) is created when contact is added
if (local == null) {
throw new DbException();
}
return local;
@Nullable
public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
ContactId c) throws DbException {
return getProperties(txn, db.getContact(txn, c), true);
}
@Override
@Nullable
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException {
return getUpdate(txn, db.getContact(txn, c), false);
public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
ContactId c) throws DbException {
return getProperties(txn, db.getContact(txn, c), false);
}
/**
* Creates and sends an update message to the given contact. The message
* holds our own mailbox's onion, generated unique properties, and lists of
* supported Mailbox API version(s). All of which the contact needs to
* communicate with our Mailbox.
* holds our own mailbox's onion, and generated unique properties. All of
* which the contact needs to communicate with our Mailbox.
*/
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
List<MailboxVersion> serverSupports, String ownOnion)
throws DbException {
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
private void createAndSendProperties(Transaction txn,
Contact c, String ownOnion) throws DbException {
MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u);
storeMessageReplaceLatest(txn, g.getId(), p);
}
/**
* Sends an update message with empty properties to the given contact. The
* empty update indicates for the receiving contact that we don't have any
* Mailbox that they can use. It still includes the list of Mailbox API
* version(s) that we support as a client.
* Sends an empty update message to the given contact. The empty update
* indicates for the receiving contact that we no longer have a Mailbox that
* they can use.
*/
private void sendUpdateNoMailbox(Transaction txn, Contact c)
private void sendEmptyProperties(Transaction txn, Contact c)
throws DbException {
Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports);
storeMessageReplaceLatest(txn, g.getId(), u);
storeMessageReplaceLatest(txn, g.getId(), null);
}
@Nullable
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local)
throws DbException {
MailboxUpdate u = null;
private MailboxPropertiesUpdate getProperties(Transaction txn,
Contact c, boolean local) throws DbException {
MailboxPropertiesUpdate p = null;
Group g = getContactGroup(c);
try {
LatestUpdate latest = findLatest(txn, g.getId(), local);
if (latest != null) {
BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId);
u = parseUpdate(body);
p = parseProperties(body);
}
} catch (FormatException e) {
throw new DbException(e);
}
return u;
return p;
}
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u) throws DbException {
@Nullable MailboxPropertiesUpdate p) throws DbException {
try {
LatestUpdate latest = findLatest(txn, g, true);
long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
encodeProperties(version, p));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
@@ -323,36 +266,23 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
return null;
}
private BdfList encodeProperties(long version, MailboxUpdate u) {
private BdfList encodeProperties(long version,
@Nullable MailboxPropertiesUpdate p) {
BdfDictionary dict = new BdfDictionary();
BdfList serverSupports = new BdfList();
if (u.hasMailbox()) {
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u;
MailboxProperties properties = um.getMailboxProperties();
serverSupports = encodeSupportsList(properties.getServerSupports());
dict.put(PROP_KEY_ONION, properties.getOnion());
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
if (p != null) {
dict.put(PROP_KEY_ONION, p.getOnion());
dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
}
return BdfList.of(version, encodeSupportsList(u.getClientSupports()),
serverSupports, dict);
return BdfList.of(version, dict);
}
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) {
BdfList supports = new BdfList();
for (MailboxVersion version : supportsList) {
supports.add(BdfList.of(version.getMajor(), version.getMinor()));
}
return supports;
}
private MailboxUpdate parseUpdate(BdfList body) throws FormatException {
BdfList clientSupports = body.getList(1);
BdfList serverSupports = body.getList(2);
BdfDictionary dict = body.getDictionary(3);
return clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dict);
@Nullable
private MailboxPropertiesUpdate parseProperties(BdfList body)
throws FormatException {
BdfDictionary dict = body.getDictionary(1);
return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
}
private Group getContactGroup(Contact c) {

View File

@@ -15,15 +15,15 @@ import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
class MailboxUpdateValidator extends BdfMessageValidator {
class MailboxPropertyValidator extends BdfMessageValidator {
MailboxUpdateValidator(ClientHelper clientHelper,
MailboxPropertyValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@@ -31,19 +31,14 @@ class MailboxUpdateValidator extends BdfMessageValidator {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// Version, Properties, clientSupports, serverSupports
checkSize(body, 4);
// Version, properties
checkSize(body, 2);
// Version
long version = body.getLong(0);
if (version < 0) throw new FormatException();
// clientSupports
BdfList clientSupports = body.getList(1);
// serverSupports
BdfList serverSupports = body.getList(2);
// Properties
BdfDictionary dictionary = body.getDictionary(3);
clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dictionary);
BdfDictionary dictionary = body.getDictionary(1);
clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
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.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxProblemEvent;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
@@ -23,7 +22,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@@ -32,7 +30,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
// Package access for testing
static final String SETTINGS_NAMESPACE = "mailbox";
// TODO: This currently stores the base URL, not the 56-char onion address
static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token";
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
@@ -61,10 +58,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
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 {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, serverSupports);
return new MailboxProperties(onion, tokenId, true, serverSupports);
} catch (InvalidMailboxIdException e) {
throw new DbException(e);
}
@@ -86,18 +92,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
}
}
@Override
public void removeOwnMailboxProperties(Transaction txn) throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn);
hook.mailboxPaired(txn, p.getOnion());
}
}
@@ -108,28 +103,18 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
List<MailboxVersion> serverSupports;
try {
serverSupports = parseServerSupports(s);
} catch (DbException e) {
serverSupports = emptyList();
}
return new MailboxStatus(lastAttempt, lastSuccess, attempts,
serverSupports);
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
Settings s = new Settings();
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
MailboxStatus status = new MailboxStatus(now, now, 0);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@@ -144,11 +129,8 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts,
serverSupports);
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
}
@Override
@@ -170,19 +152,4 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
if (isNullOrEmpty(filename)) return null;
return filename;
}
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,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,75 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(OwnMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
return () -> {
try {
return checkConnectivityAndStoreResult(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
return true; // Retry
}
};
}
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now));
// Call the observers and cache the result
onConnectivityCheckSucceeded(now);
return false; // Don't retry
} catch (IOException | ApiException e) {
LOG.warning("Own mailbox is unreachable");
logException(LOG, WARNING, e);
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordFailedConnectionAttempt(txn, now));
}
return true; // Retry
}
}

View File

@@ -1,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,10 +1,8 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
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.TransportId;
import javax.annotation.concurrent.Immutable;
@@ -13,9 +11,8 @@ import javax.annotation.concurrent.Immutable;
class BackoffFactoryImpl implements BackoffFactory {
@Override
public Backoff createBackoff(EventBus eventBus, TransportId transportId,
int minInterval, int maxInterval, double base) {
return new BackoffImpl(eventBus, transportId, minInterval, maxInterval,
base);
public Backoff createBackoff(int minInterval, int maxInterval,
double base) {
return new BackoffImpl(minInterval, maxInterval, base);
}
}

View File

@@ -1,10 +1,7 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import java.util.concurrent.atomic.AtomicInteger;
@@ -14,16 +11,11 @@ import javax.annotation.concurrent.ThreadSafe;
@NotNullByDefault
class BackoffImpl implements Backoff {
private final EventBus eventBus;
private final TransportId transportId;
private final int minInterval, maxInterval;
private final double base;
private final AtomicInteger backoff;
BackoffImpl(EventBus eventBus, TransportId transportId,
int minInterval, int maxInterval, double base) {
this.eventBus = eventBus;
this.transportId = transportId;
BackoffImpl(int minInterval, int maxInterval, double base) {
this.minInterval = minInterval;
this.maxInterval = maxInterval;
this.base = base;
@@ -45,9 +37,6 @@ class BackoffImpl implements Backoff {
@Override
public void reset() {
int old = backoff.getAndSet(0);
if (old > 0) {
eventBus.broadcast(new PollingIntervalDecreasedEvent(transportId));
}
backoff.set(0);
}
}

View File

@@ -20,7 +20,6 @@ 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.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
@@ -363,11 +362,6 @@ class PluginManagerImpl implements PluginManager, Service {
}
}
@Override
public void pollingIntervalDecreased() {
eventBus.broadcast(new PollingIntervalDecreasedEvent(id));
}
@Override
public void handleConnection(DuplexTransportConnection d) {
connectionManager.manageIncomingConnection(id, d);

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
@@ -20,7 +19,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
@@ -28,6 +27,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
@@ -107,14 +107,10 @@ class PollerImpl implements Poller, EventListener {
if (!c.isIncoming() && c.isException()) {
connectToContact(c.getContactId(), c.getTransportId());
}
} else if (e instanceof PollingIntervalDecreasedEvent) {
PollingIntervalDecreasedEvent p = (PollingIntervalDecreasedEvent) e;
TransportId t = p.getTransportId();
if (LOG.isLoggable(INFO)) {
LOG.info("Polling interval decreased for " + t);
}
// Reschedule polling
reschedule(t);
} else if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
} else if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e;
// Poll the newly activated transport
@@ -232,7 +228,7 @@ class PollerImpl implements Poller, EventListener {
if (!connected.contains(c))
properties.add(new Pair<>(e.getValue(), new Handler(c, t)));
}
p.poll(properties);
if (!properties.isEmpty()) p.poll(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
}

View File

@@ -335,7 +335,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (properties.isEmpty() || getState() != ACTIVE) return;
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());

View File

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

View File

@@ -11,7 +11,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
@@ -30,6 +29,11 @@ abstract class FilePlugin implements SimplexPlugin {
protected final PluginCallback callback;
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) {
this.callback = callback;
this.maxLatency = maxLatency;
@@ -46,8 +50,9 @@ abstract class FilePlugin implements SimplexPlugin {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null;
try {
FileInputStream in = new FileInputStream(path);
return new TransportInputStreamReader(in);
File file = new File(path);
FileInputStream in = new FileInputStream(file);
return new FileTransportReader(file, in, this);
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
@@ -65,8 +70,8 @@ abstract class FilePlugin implements SimplexPlugin {
LOG.info("Failed to create file");
return null;
}
OutputStream out = new FileOutputStream(file);
return new TransportOutputStreamWriter(this, out);
FileOutputStream out = new FileOutputStream(file);
return new FileTransportWriter(file, out, this);
} catch (IOException e) {
logException(LOG, WARNING, e);
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,39 +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 java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
@NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@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

@@ -56,8 +56,8 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, wakefulIoExecutor,
backoff, callback, MAX_LATENCY, MAX_IDLE_TIME,
CONNECTION_TIMEOUT);

View File

@@ -246,7 +246,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (properties.isEmpty() || getState() != ACTIVE) return;
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());

View File

@@ -60,8 +60,8 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
PortMapper portMapper = new PortMapperImpl(shutdownManager);
WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, wakefulIoExecutor,
backoff, portMapper, callback, MAX_LATENCY, MAX_IDLE_TIME,

View File

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

View File

@@ -14,6 +14,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
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.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
@@ -34,8 +35,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES));
private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(MEEK_BRIDGES));
@Inject
CircumventionProviderImpl() {
@@ -57,8 +58,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK);
} else if (MEEK_COUNTRIES.contains(countryCode)) {
return singletonList(MEEK);
} else {
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.TorControlConnection;
import net.freehaven.tor.control.TorNotRunningException;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
@@ -18,6 +17,7 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
@@ -25,7 +25,6 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -67,7 +66,6 @@ import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
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;
@@ -108,7 +106,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault
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 = {
"CIRC",
@@ -125,53 +123,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
/**
* After this many consecutive successful connections to our own hidden
* service we consider the network to be stable.
* <p>
* This constant times {@link #POLLING_INTERVAL_UNSTABLE} should be
* greater than {@link #POLLING_INTERVAL_STABLE}. This ensures that if
* we experience a network outage that isn't detected by the
* {@link NetworkManager}, and if one of our contacts comes online during
* the outage, then either the outage lasts longer than
* {@link #POLLING_INTERVAL_STABLE}, in which case we detect the outage
* by failing to connect to our own hidden service, or the outage ends
* before the contact considers their own network connection to be stable,
* in which case the contact is still trying to connect to us when the
* outage ends. Either way, we don't end up in a situation where both we
* and the contact consider our network connections to be stable and stop
* trying to connect to each other, despite both being online.
*/
private static final int STABLE_NETWORK_THRESHOLD = 10;
/**
* After this many consecutive failed connections to our own hidden service
* we consider our connection to the Tor network to be broken.
*/
private static final int BROKEN_NETWORK_THRESHOLD = 3;
/**
* How often to poll our own hidden service when the network is considered
* to be stable.
*/
private static final int POLLING_INTERVAL_STABLE =
(int) MINUTES.toMillis(10);
/**
* How often to poll our own hidden service and our contacts' hidden
* services when the network is considered to be unstable.
*/
private static final int POLLING_INTERVAL_UNSTABLE =
(int) MINUTES.toMillis(2);
protected final Executor ioExecutor;
private final Executor wakefulIoExecutor;
private final Executor ioExecutor, wakefulIoExecutor;
private final Executor connectionStatusExecutor;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final Clock clock;
private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback;
private final String architecture;
@@ -191,7 +150,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
private volatile String ownOnion = null;
protected abstract int getProcessId();
@@ -206,6 +164,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -223,6 +182,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto;
this.callback = callback;
this.architecture = architecture;
@@ -293,15 +253,34 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException 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 {
// Wait for the Tor process to start
waitForTorToStart(torProcess);
// 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();
}
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
@@ -342,8 +321,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Tor has already built a circuit");
state.getAndSetCircuitBuilt(true);
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new PluginException(e);
}
@@ -417,7 +394,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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(" ");
strb.append(value);
@@ -425,17 +402,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -466,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() {
ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it
@@ -512,6 +468,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor
acceptContactConnections(ss);
});
@@ -535,8 +492,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} else {
response = controlConnection.addOnion(privKey, portLines);
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
@@ -550,7 +505,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
String onion3 = response.get(HS_ADDRESS);
ownOnion = onion3;
if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(onion3));
}
@@ -579,44 +533,37 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
LOG.info("Connection received");
backoff.reset();
callback.handleConnection(new TorTransportConnection(this, s));
}
}
protected void enableNetwork(boolean enable) throws IOException {
state.enableNetwork(enable);
try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
throws IOException {
try {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeTypes.contains(MEEK)) {
conf.add("ClientTransportPlugin meek_lite exec " +
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");
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeTypes.contains(MEEK)) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
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");
}
}
@@ -630,8 +577,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
@@ -655,58 +600,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public int getPollingInterval() {
if (state.isNetworkStable()) {
LOG.info("Using stable polling interval");
return POLLING_INTERVAL_STABLE;
} else {
LOG.info("Using unstable polling interval");
return POLLING_INTERVAL_UNSTABLE;
}
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
String ownOnion = this.ownOnion;
if (ownOnion == null) {
// Our own hidden service hasn't been created yet
pollContacts(properties);
} else {
// If the network is unstable, poll our contacts
boolean stable = state.isNetworkStable();
if (!stable) pollContacts(properties);
// Poll our own hidden service to check if the network is stable
wakefulIoExecutor.execute(() -> {
LOG.info("Connecting to own hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, ownOnion);
DuplexTransportConnection d = createConnection(p);
if (d == null) {
LOG.info("Could not connect to own hidden service");
state.onStabilityCheckFailed();
// If the network was previously considered stable then
// we didn't poll our contacts above, so poll them now
if (stable) pollContacts(properties);
} else {
LOG.info("Connected to own hidden service");
// Close the connection (this will cause the other end of
// the connection to log an EOFException)
try {
d.getWriter().dispose(false);
d.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
state.onStabilityCheckSucceeded();
}
});
}
}
private void pollContacts(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
if (properties.isEmpty() || getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
}
@@ -715,7 +616,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connect(TransportProperties p, ConnectionHandler h) {
wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p);
if (d != null) h.handleConnection(d);
if (d != null) {
backoff.reset();
h.handleConnection(d);
}
});
}
@@ -804,11 +708,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
Map<Integer, String> portLines =
singletonMap(80, "127.0.0.1:" + port);
try {
controlConnection.addOnion(blob, portLines);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
controlConnection.addOnion(blob, portLines);
return new RendezvousEndpoint() {
@Override
@@ -818,11 +718,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void close() throws IOException {
try {
controlConnection.delOnion(localOnion);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
controlConnection.delOnion(localOnion);
tryToClose(ss, LOG, WARNING);
}
};
@@ -838,6 +734,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@@ -849,8 +746,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void orConnStatus(String status, String orName) {
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();
if ((status.equals("FAILED") || status.equals("CLOSED")) &&
state.getNumOrConnections() == 0) {
// Check whether we've lost connectivity
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
}
@Override
@@ -865,7 +772,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (msg.startsWith("Switching to guard context")) {
state.resetNetworkStability();
state.onSwitchingGuardContext();
}
}
@@ -893,8 +800,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped");
state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (!state.getAndSetCircuitBuilt(true)) LOG.info("Circuit built");
if (!state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built");
@@ -939,19 +850,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
if (c.getTransportId().equals(getId())
&& !c.isIncoming() && c.isException()) {
LOG.info("Outgoing connection closed with exception");
// The failure may indicate that the network is unstable
state.resetNetworkStability();
}
} else if (e instanceof SettingsUpdatedEvent) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
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(),
batteryManager.isCharging());
}
@@ -964,7 +872,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void disableAndReenableNetwork() {
private void disableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
if (state.isTorRunning()) enableNetwork(false);
@@ -972,8 +880,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
logException(LOG, WARNING, ex);
}
});
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
private void updateConnectionStatus(NetworkStatus status,
@@ -1072,20 +978,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void enableConnectionPadding(boolean enable) throws IOException {
try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
private void useIpv6(boolean ipv6Only) throws IOException {
try {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
}
@ThreadSafe
@@ -1104,26 +1002,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this")
private int reasonsDisabled = 0;
/**
* The number of consecutive successful (positive) or failed (negative)
* connections to our own hidden service.
*/
@GuardedBy("this")
private int networkStability = 0;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
@GuardedBy("this")
private int orConnectionsConnected = 0;
private int orConnectionsPending = 0, orConnectionsConnected = 0;
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@@ -1181,77 +1071,63 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
? ACTIVE : ENABLING;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
private synchronized int getReasonsDisabled() {
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() {
int oldConnected = orConnectionsConnected;
orConnectionsPending--;
if (orConnectionsPending < 0) {
LOG.warning("Count was zero before connection connected");
orConnectionsPending = 0;
}
orConnectionsConnected++;
logOrConnections();
if (oldConnected == 0) callback.pluginStateChanged(getState());
}
private synchronized void onOrConnectionClosed() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected--;
if (orConnectionsConnected < 0) {
LOG.warning("Count was zero before connection closed");
orConnectionsConnected = 0;
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
resetNetworkStability();
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")
private void logOrConnections() {
if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
private synchronized boolean isNetworkStable() {
return networkStability >= STABLE_NETWORK_THRESHOLD;
}
private synchronized void onStabilityCheckSucceeded() {
if (networkStability <= 0) networkStability = 1;
else networkStability++;
logNetworkStability();
}
private synchronized void onStabilityCheckFailed() {
if (networkStability >= 0) networkStability = -1;
else networkStability--;
if (networkStability <= -BROKEN_NETWORK_THRESHOLD) {
LOG.warning("Connection to Tor appears to be broken");
resetNetworkStability();
disableAndReenableNetwork();
} else {
logNetworkStability();
}
}
private synchronized void resetNetworkStability() {
int old = networkStability;
networkStability = 0;
logNetworkStability();
if (old >= STABLE_NETWORK_THRESHOLD) {
callback.pollingIntervalDecreased();
}
}
@GuardedBy("this")
private void logNetworkStability() {
if (LOG.isLoggable(INFO)) {
LOG.info("Network stability score " + networkStability);
LOG.info("OR connections: " + orConnectionsPending
+ " pending, " + orConnectionsConnected + " connected");
}
}
}

View File

@@ -1,124 +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.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
protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
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,
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.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(
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);
}
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
TorPlugin plugin = createPluginInstance(torRendezvousCrypto,
callback, architecture);
eventBus.addListener(plugin);
return plugin;
}
}

View File

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

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.rendezvous;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager;
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.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import java.security.GeneralSecurityException;

View File

@@ -10,7 +10,6 @@ import dagger.Module;
import dagger.Provides;
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;
@Module
@@ -21,6 +20,6 @@ public class SocksModule {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
torSocksPort);
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"
};
@SuppressWarnings("MismatchedReadAndWriteOfArray")
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private final SocketAddress proxy;
private final int connectToProxyTimeout;
private final int extraConnectTimeout, extraSocketTimeout;
private final int connectToProxyTimeout, extraSocketTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
int extraConnectTimeout, int extraSocketTimeout) {
int extraSocketTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraConnectTimeout = extraConnectTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@@ -69,7 +66,7 @@ class SocksSocket extends Socket {
// Use the supplied timeout temporarily, plus any configured extra
int oldTimeout = getSoTimeout();
setSoTimeout(timeout + extraConnectTimeout);
setSoTimeout(timeout + extraSocketTimeout);
// Connect to the endpoint via the proxy
sendConnectRequest(out, host, port);

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