mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
127 Commits
print-db-s
...
beta-1.4.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e61827fe6 | ||
|
|
2be93f6a49 | ||
|
|
5eb994d3e8 | ||
|
|
f0c9819332 | ||
|
|
971dbf5df2 | ||
|
|
43a83df342 | ||
|
|
0092f38bab | ||
|
|
285a5f2928 | ||
|
|
804049209d | ||
|
|
2b1aed6caa | ||
|
|
44b0955b9d | ||
|
|
d43ef463a6 | ||
|
|
34337486e9 | ||
|
|
3ebbb2a8cf | ||
|
|
54339afab8 | ||
|
|
6c19b22aab | ||
|
|
6b790b59fa | ||
|
|
8b61a0279b | ||
|
|
94ce6bbb2c | ||
|
|
845d505d2b | ||
|
|
6358518f88 | ||
|
|
ef6e3bb2a7 | ||
|
|
8ec998f645 | ||
|
|
f75d63fc46 | ||
|
|
0c22c25995 | ||
|
|
7e249ecf70 | ||
|
|
274963d9d1 | ||
|
|
18b3865a86 | ||
|
|
f08688708a | ||
|
|
c37f6069c7 | ||
|
|
c8caae49f1 | ||
|
|
670cc34b12 | ||
|
|
f387c3801b | ||
|
|
aa759a636e | ||
|
|
0b85aca932 | ||
|
|
d4cdedeed7 | ||
|
|
9b10c12f23 | ||
|
|
2bf490b973 | ||
|
|
d2f25f2ebe | ||
|
|
b3dcde9187 | ||
|
|
241e5e9f6e | ||
|
|
c59524df65 | ||
|
|
4467f9e260 | ||
|
|
7e215e7f84 | ||
|
|
601ff50294 | ||
|
|
9f839d9d12 | ||
|
|
1e4c28a30a | ||
|
|
bc0f9a984c | ||
|
|
15e0abffb0 | ||
|
|
5254efb630 | ||
|
|
df22df22a0 | ||
|
|
23681ff7f7 | ||
|
|
57bebc0b87 | ||
|
|
82057da962 | ||
|
|
00b7518e49 | ||
|
|
418ab99a3c | ||
|
|
49c14af0dc | ||
|
|
3f7aed7886 | ||
|
|
d2728dd29b | ||
|
|
84afc6d934 | ||
|
|
a42d9eec1c | ||
|
|
5d5d8d206c | ||
|
|
5237df32e3 | ||
|
|
72e376f152 | ||
|
|
4d685a2617 | ||
|
|
16ab48d009 | ||
|
|
095bebf524 | ||
|
|
b67d9935c7 | ||
|
|
34aea945cb | ||
|
|
a82666b8bd | ||
|
|
e614046662 | ||
|
|
0691354952 | ||
|
|
aa997a9c64 | ||
|
|
f05cbac20a | ||
|
|
39c74f1363 | ||
|
|
2411c82d9c | ||
|
|
f43839dbb3 | ||
|
|
3138213f39 | ||
|
|
d080af4b7a | ||
|
|
9d19761dbe | ||
|
|
fa3a5be083 | ||
|
|
fa3db0f888 | ||
|
|
4b7ee62190 | ||
|
|
9d3c33fdbc | ||
|
|
37d4ca84f7 | ||
|
|
1b58d986ae | ||
|
|
784c7416ec | ||
|
|
7536f16c61 | ||
|
|
ab628c1921 | ||
|
|
85e53479f2 | ||
|
|
116ee97056 | ||
|
|
78938f1ac6 | ||
|
|
afff66eaff | ||
|
|
8c33ea5a6b | ||
|
|
96228c1fd0 | ||
|
|
eb6a5fe63e | ||
|
|
a8624cd507 | ||
|
|
e7fc37d81e | ||
|
|
7bd220f18d | ||
|
|
7f581fee15 | ||
|
|
383056d37e | ||
|
|
23316f5e9c | ||
|
|
dea05c85a2 | ||
|
|
b36066514b | ||
|
|
f9403782a2 | ||
|
|
174ca3cfb8 | ||
|
|
961af66c8e | ||
|
|
a86ea454d0 | ||
|
|
a7877bf7ee | ||
|
|
62ae0f745b | ||
|
|
f83abbe63d | ||
|
|
e0b6b8435d | ||
|
|
d3c7832245 | ||
|
|
cc4978c2b1 | ||
|
|
a043e8b1cf | ||
|
|
97ba18cfb2 | ||
|
|
bc013296f6 | ||
|
|
c1fabcd46b | ||
|
|
3c08e86822 | ||
|
|
de2c9670d5 | ||
|
|
9632754274 | ||
|
|
b275a0ffff | ||
|
|
edd270abf3 | ||
|
|
47d412dd0a | ||
|
|
5d952ff68e | ||
|
|
9304a6b266 | ||
|
|
a99ec5ed51 |
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10406
|
versionCode 10408
|
||||||
versionName "1.4.6"
|
versionName "1.4.8"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import android.net.LinkAddress;
|
|||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
@@ -21,7 +24,6 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -38,6 +40,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
@@ -111,15 +114,37 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NetworkStatus getNetworkStatus() {
|
public NetworkStatus getNetworkStatus() {
|
||||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
// https://issuetracker.google.com/issues/175055271
|
||||||
boolean connected = net != null && net.isConnected();
|
try {
|
||||||
boolean wifi = false, ipv6Only = false;
|
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||||
if (connected) {
|
boolean connected = net != null && net.isConnected();
|
||||||
wifi = net.getType() == TYPE_WIFI;
|
boolean wifi = false, ipv6Only = false;
|
||||||
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
if (connected) {
|
||||||
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
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);
|
||||||
}
|
}
|
||||||
return new NetworkStatus(connected, wifi, ipv6Only);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,23 +155,29 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
*/
|
*/
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private boolean isActiveNetworkIpv6Only() {
|
private boolean isActiveNetworkIpv6Only() {
|
||||||
Network net = connectivityManager.getActiveNetwork();
|
// https://issuetracker.google.com/issues/175055271
|
||||||
if (net == null) {
|
try {
|
||||||
LOG.info("No active network");
|
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);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LinkProperties props = connectivityManager.getLinkProperties(net);
|
|
||||||
if (props == null) {
|
|
||||||
LOG.info("No link properties for active network");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean hasIpv6Unicast = false;
|
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
|
||||||
InetAddress addr = linkAddress.getAddress();
|
|
||||||
if (addr instanceof Inet4Address) return false;
|
|
||||||
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
|
|
||||||
}
|
|
||||||
return hasIpv6Unicast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,13 +32,22 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
|||||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
try {
|
||||||
|
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
return app.getContentResolver().openOutputStream(Uri.parse(uri), "wt");
|
try {
|
||||||
|
return app.getContentResolver()
|
||||||
|
.openOutputStream(Uri.parse(uri), "wt");
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,16 +175,24 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
@Nullable
|
@Nullable
|
||||||
private InetAddress getWifiClientIpv6Address() {
|
private InetAddress getWifiClientIpv6Address() {
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
// https://issuetracker.google.com/issues/175055271
|
||||||
NetworkCapabilities caps =
|
try {
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
|
NetworkCapabilities caps =
|
||||||
LinkProperties props = connectivityManager.getLinkProperties(net);
|
connectivityManager.getNetworkCapabilities(net);
|
||||||
if (props == null) continue;
|
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) {
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
continue;
|
||||||
InetAddress addr = linkAddress.getAddress();
|
}
|
||||||
if (isIpv6LinkLocalAddress(addr)) return addr;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -227,12 +235,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
// network's socket factory may try to connect via another network
|
// network's socket factory may try to connect via another network
|
||||||
private SocketFactory getSocketFactory() {
|
private SocketFactory getSocketFactory() {
|
||||||
if (SDK_INT < 21) return SocketFactory.getDefault();
|
if (SDK_INT < 21) return SocketFactory.getDefault();
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
// https://issuetracker.google.com/issues/175055271
|
||||||
NetworkCapabilities caps =
|
try {
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
NetworkCapabilities caps =
|
||||||
return net.getSocketFactory();
|
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");
|
LOG.warning("Could not find suitable socket factory");
|
||||||
return SocketFactory.getDefault();
|
return SocketFactory.getDefault();
|
||||||
|
|||||||
@@ -11,62 +11,35 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
|
||||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(AndroidTorPluginFactory.class.getName());
|
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
|
||||||
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
|
|
||||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
|
||||||
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
|
||||||
private static final double BACKOFF_BASE = 1.2;
|
|
||||||
|
|
||||||
private final Executor ioExecutor, wakefulIoExecutor;
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
private final NetworkManager networkManager;
|
|
||||||
private final LocationUtils locationUtils;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final SocketFactory torSocketFactory;
|
|
||||||
private final BackoffFactory backoffFactory;
|
|
||||||
private final ResourceProvider resourceProvider;
|
|
||||||
private final CircumventionProvider circumventionProvider;
|
|
||||||
private final BatteryManager batteryManager;
|
|
||||||
private final AndroidWakeLockManager wakeLockManager;
|
private final AndroidWakeLockManager wakeLockManager;
|
||||||
private final Clock clock;
|
|
||||||
private final File torDirectory;
|
|
||||||
private int torSocksPort;
|
|
||||||
private int torControlPort;
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
Application app,
|
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
@@ -75,80 +48,43 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
ResourceProvider resourceProvider,
|
ResourceProvider resourceProvider,
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
AndroidWakeLockManager wakeLockManager,
|
|
||||||
Clock clock,
|
Clock clock,
|
||||||
|
CryptoComponent crypto,
|
||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort,
|
@TorControlPort int torControlPort,
|
||||||
CryptoComponent crypto) {
|
Application app,
|
||||||
this.ioExecutor = ioExecutor;
|
AndroidWakeLockManager wakeLockManager) {
|
||||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||||
|
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||||
|
circumventionProvider, batteryManager, clock, crypto,
|
||||||
|
torDirectory, torSocksPort, torControlPort);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.networkManager = networkManager;
|
|
||||||
this.locationUtils = locationUtils;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.torSocketFactory = torSocketFactory;
|
|
||||||
this.backoffFactory = backoffFactory;
|
|
||||||
this.resourceProvider = resourceProvider;
|
|
||||||
this.circumventionProvider = circumventionProvider;
|
|
||||||
this.batteryManager = batteryManager;
|
|
||||||
this.wakeLockManager = wakeLockManager;
|
this.wakeLockManager = wakeLockManager;
|
||||||
this.clock = clock;
|
|
||||||
this.torDirectory = torDirectory;
|
|
||||||
this.torSocksPort = torSocksPort;
|
|
||||||
this.torControlPort = torControlPort;
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public TransportId getId() {
|
String getArchitectureForTorBinary() {
|
||||||
return TorConstants.ID;
|
for (String abi : getSupportedArchitectures()) {
|
||||||
}
|
if (abi.startsWith("x86_64")) return "x86_64_pie";
|
||||||
|
else if (abi.startsWith("x86")) return "x86_pie";
|
||||||
@Override
|
else if (abi.startsWith("arm64")) return "arm64_pie";
|
||||||
public long getMaxLatency() {
|
else if (abi.startsWith("armeabi")) return "arm_pie";
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (architecture == null) {
|
return null;
|
||||||
LOG.info("Tor is not supported on this architecture");
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Use position-independent executable
|
|
||||||
architecture += "_pie";
|
|
||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
@Override
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
TorPlugin createPluginInstance(Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto =
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
new TorRendezvousCryptoImpl(crypto);
|
String architecture) {
|
||||||
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
|
return new AndroidTorPlugin(ioExecutor,
|
||||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocketFactory, clock, resourceProvider,
|
||||||
circumventionProvider, batteryManager, wakeLockManager,
|
circumventionProvider, batteryManager, wakeLockManager,
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
backoff, torRendezvousCrypto, callback, architecture,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||||
torControlPort);
|
torControlPort);
|
||||||
eventBus.addListener(plugin);
|
|
||||||
return plugin;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.content.Intent;
|
|||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.AlarmListener;
|
import org.briarproject.bramble.api.system.AlarmListener;
|
||||||
@@ -116,10 +117,12 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
||||||
Runnable wakeful = () ->
|
Runnable wakeful = () ->
|
||||||
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
||||||
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
// Acquire the lock before scheduling the check to ensure the check
|
||||||
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
|
// doesn't access the task queue before the task has been added
|
||||||
cancelled);
|
ScheduledTask s;
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
||||||
|
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
|
||||||
tasks.add(s);
|
tasks.add(s);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
@@ -136,6 +139,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
return schedule(wrapped, executor, delay, unit, cancelled);
|
return schedule(wrapped, executor, delay, unit, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
||||||
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
||||||
this::runDueTasks, "TaskScheduler");
|
this::runDueTasks, "TaskScheduler");
|
||||||
@@ -206,7 +210,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
private final Future<?> check;
|
private final Future<?> check;
|
||||||
private final AtomicBoolean cancelled;
|
private final AtomicBoolean cancelled;
|
||||||
|
|
||||||
public ScheduledTask(Runnable task, long dueMillis,
|
private ScheduledTask(Runnable task, long dueMillis,
|
||||||
Future<?> check, AtomicBoolean cancelled) {
|
Future<?> check, AtomicBoolean cancelled) {
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.dueMillis = dueMillis;
|
this.dueMillis = dueMillis;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Looper;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
@@ -134,4 +135,8 @@ public class AndroidUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isUiThread() {
|
||||||
|
return Looper.myLooper() == Looper.getMainLooper();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
public interface Cancellable {
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
package org.briarproject.bramble.api;
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public abstract class StringMap extends Hashtable<String, String> {
|
public abstract class StringMap extends Hashtable<String, String> {
|
||||||
|
|
||||||
protected StringMap(Map<String, String> m) {
|
protected StringMap(Map<String, String> m) {
|
||||||
@@ -52,4 +58,31 @@ public abstract class StringMap extends Hashtable<String, String> {
|
|||||||
public void putLong(String key, long value) {
|
public void putLong(String key, long value) {
|
||||||
put(key, String.valueOf(value));
|
put(key, String.valueOf(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public int[] getIntArray(String key) {
|
||||||
|
String s = get(key);
|
||||||
|
if (s == null) return null;
|
||||||
|
// Handle empty string because "".split(",") returns {""}
|
||||||
|
if (s.length() == 0) return new int[0];
|
||||||
|
String[] intStrings = s.split(",");
|
||||||
|
int[] ints = new int[intStrings.length];
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < ints.length; i++) {
|
||||||
|
ints[i] = Integer.parseInt(intStrings[i]);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putIntArray(String key, int[] value) {
|
||||||
|
List<String> intStrings = new ArrayList<>();
|
||||||
|
for (int integer : value) {
|
||||||
|
intStrings.add(String.valueOf(integer));
|
||||||
|
}
|
||||||
|
// Puts empty string if input array value is empty
|
||||||
|
put(key, StringUtils.join(intStrings, ","));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -19,10 +20,9 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ClientHelper {
|
public interface ClientHelper {
|
||||||
|
|
||||||
@@ -127,16 +127,17 @@ public interface ClientHelper {
|
|||||||
BdfDictionary properties) throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and validate the property dictionary of a Mailbox property update
|
* Parse and validate the elements of a Mailbox update message.
|
||||||
* message.
|
|
||||||
*
|
*
|
||||||
* @return the properties for using the Mailbox, or null if there is no
|
* @return the parsed update message
|
||||||
* Mailbox available
|
* @throws FormatException if the message elements are invalid
|
||||||
* @throws FormatException if the properties are not valid
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
|
||||||
MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
BdfList serverSupports, BdfDictionary properties)
|
||||||
BdfDictionary properties) throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
|
List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
|
||||||
|
throws FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the contact ID from the group metadata of the given contact
|
* Retrieves the contact ID from the group metadata of the given contact
|
||||||
|
|||||||
@@ -33,11 +33,18 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates the database implementation and exposes high-level operations
|
* Encapsulates the database implementation and exposes high-level operations
|
||||||
* to other components.
|
* to other components.
|
||||||
|
* <p>
|
||||||
|
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
|
||||||
|
* {@link #close()} methods, which must not be called concurrently, the
|
||||||
|
* database can be accessed from any thread. See {@link TransactionManager}
|
||||||
|
* for locking behaviour.
|
||||||
*/
|
*/
|
||||||
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface DatabaseComponent extends TransactionManager {
|
public interface DatabaseComponent extends TransactionManager {
|
||||||
|
|
||||||
@@ -193,26 +200,15 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a batch of messages for the given contact, with a total length
|
* Returns a batch of messages for the given contact, for transmission over
|
||||||
* less than or equal to the given length, for transmission over a
|
* a transport with the given maximum latency. The total length of the
|
||||||
* transport with the given maximum latency. Returns null if there are no
|
* messages, including record headers, will be no more than the given
|
||||||
* sendable messages that fit in the given length.
|
* capacity. Returns null if there are no sendable messages that would fit
|
||||||
|
* in the given capacity.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
int maxLength, long maxLatency) throws DbException;
|
long capacity, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a batch of messages for the given contact containing the
|
|
||||||
* messages with the given IDs, for transmission over a transport with
|
|
||||||
* the given maximum latency.
|
|
||||||
* <p/>
|
|
||||||
* If any of the given messages are not in the database or are not visible
|
|
||||||
* to the contact, they are omitted from the batch without throwing an
|
|
||||||
* exception.
|
|
||||||
*/
|
|
||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
|
||||||
Collection<MessageId> ids, long maxLatency) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an offer for the given contact for transmission over a
|
* Returns an offer for the given contact for transmission over a
|
||||||
@@ -232,15 +228,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a batch of messages for the given contact, with a total length
|
* Returns a batch of messages for the given contact, for transmission over
|
||||||
* less than or equal to the given length, for transmission over a
|
* a transport with the given maximum latency. Only messages that have been
|
||||||
* transport with the given maximum latency. Only messages that have been
|
* requested by the contact are returned. The total length of the messages,
|
||||||
* requested by the contact are returned. Returns null if there are no
|
* including record headers, will be no more than the given capacity.
|
||||||
* sendable messages that fit in the given length.
|
* Returns null if there are no sendable messages that have been requested
|
||||||
|
* by the contact and would fit in the given capacity.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
|
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
|
||||||
int maxLength, long maxLatency) throws DbException;
|
long capacity, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contact with the given ID.
|
* Returns the contact with the given ID.
|
||||||
@@ -344,6 +341,30 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||||
Metadata query) throws DbException;
|
Metadata query) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of some messages received from the given contact that
|
||||||
|
* need to be acknowledged, up to the given number of messages.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
|
||||||
|
int maxMessages) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
|
* given contact over a transport with the given maximum latency. The total
|
||||||
|
* length of the messages including record headers will be no more than the
|
||||||
|
* given capacity.
|
||||||
|
* <p/>
|
||||||
|
* Unlike {@link #getUnackedMessagesToSend(Transaction, ContactId)} this
|
||||||
|
* method does not return messages that have already been sent unless they
|
||||||
|
* are due for retransmission.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Collection<MessageId> getMessagesToSend(Transaction txn, ContactId c,
|
||||||
|
long capacity, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -460,15 +481,30 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message with the given ID for transmission to the given
|
||||||
|
* contact over a transport with the given maximum latency. Returns null
|
||||||
|
* if the message is no longer visible to the contact.
|
||||||
|
*
|
||||||
|
* @param markAsSent True if the message should be marked as sent.
|
||||||
|
* If false it can be marked as sent by calling
|
||||||
|
* {@link #setMessagesSent(Transaction, ContactId, Collection, long)}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Message getMessageToSend(Transaction txn, ContactId c, MessageId m,
|
||||||
|
long maxLatency, boolean markAsSent) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all messages that are eligible to be sent to the
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
* given contact, together with their raw lengths. This may include
|
* given contact.
|
||||||
* messages that have already been sent and are not yet due for
|
* <p>
|
||||||
* retransmission.
|
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, long)}
|
||||||
|
* this method may return messages that have already been sent and are
|
||||||
|
* not yet due for retransmission.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
Collection<MessageId> getUnackedMessagesToSend(Transaction txn,
|
||||||
ContactId c) throws DbException;
|
ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -648,6 +684,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records an ack for the given messages as having been sent to the given
|
||||||
|
* contact.
|
||||||
|
*/
|
||||||
|
void setAckSent(Transaction txn, ContactId c, Collection<MessageId> acked)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cleanup timer duration for the given message. This does not
|
* Sets the cleanup timer duration for the given message. This does not
|
||||||
* start the message's cleanup timer.
|
* start the message's cleanup timer.
|
||||||
@@ -694,6 +737,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void setMessageState(Transaction txn, MessageId m, MessageState state)
|
void setMessageState(Transaction txn, MessageId m, MessageState state)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the given messages as having been sent to the given contact
|
||||||
|
* over a transport with the given maximum latency.
|
||||||
|
*/
|
||||||
|
void setMessagesSent(Transaction txn, ContactId c,
|
||||||
|
Collection<MessageId> sent, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds dependencies for a message
|
* Adds dependencies for a message
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
* submitted, tasks are not run concurrently, and submitting a task will never
|
* submitted, tasks are not run concurrently, and submitting a task will never
|
||||||
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
|
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
|
||||||
* discarded.
|
* discarded.
|
||||||
|
* <p>
|
||||||
|
* It is not mandatory to use this executor for database tasks. The database
|
||||||
|
* can be accessed from any thread, but this executor's guarantee that tasks
|
||||||
|
* are run in the order they're submitted may be useful in some cases.
|
||||||
*/
|
*/
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Target({FIELD, METHOD, PARAMETER})
|
@Target({FIELD, METHOD, PARAMETER})
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public class Transaction {
|
|||||||
/**
|
/**
|
||||||
* Attaches an event to be broadcast when the transaction has been
|
* Attaches an event to be broadcast when the transaction has been
|
||||||
* committed. The event will be broadcast on the {@link EventExecutor}.
|
* committed. The event will be broadcast on the {@link EventExecutor}.
|
||||||
|
* Events and {@link #attach(Runnable) tasks} are submitted to the
|
||||||
|
* {@link EventExecutor} in the order they were attached to the
|
||||||
|
* transaction.
|
||||||
*/
|
*/
|
||||||
public void attach(Event e) {
|
public void attach(Event e) {
|
||||||
if (actions == null) actions = new ArrayList<>();
|
if (actions == null) actions = new ArrayList<>();
|
||||||
@@ -54,6 +57,9 @@ public class Transaction {
|
|||||||
/**
|
/**
|
||||||
* Attaches a task to be executed when the transaction has been
|
* Attaches a task to be executed when the transaction has been
|
||||||
* committed. The task will be run on the {@link EventExecutor}.
|
* committed. The task will be run on the {@link EventExecutor}.
|
||||||
|
* {@link #attach(Event) Events} and tasks are submitted to the
|
||||||
|
* {@link EventExecutor} in the order they were attached to the
|
||||||
|
* transaction.
|
||||||
*/
|
*/
|
||||||
public void attach(Runnable r) {
|
public void attach(Runnable r) {
|
||||||
if (actions == null) actions = new ArrayList<>();
|
if (actions == null) actions = new ArrayList<>();
|
||||||
|
|||||||
@@ -1,51 +1,95 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for managing database transactions.
|
||||||
|
* <p>
|
||||||
|
* Read-only transactions may access the database concurrently. Read-write
|
||||||
|
* transactions access the database exclusively, so starting a read-only or
|
||||||
|
* read-write transaction will block until there are no read-write
|
||||||
|
* transactions in progress.
|
||||||
|
* <p>
|
||||||
|
* Failing to {@link #endTransaction(Transaction) end} a transaction will
|
||||||
|
* prevent other callers from accessing the database, so it is recommended to
|
||||||
|
* use the {@link #transaction(boolean, DbRunnable)},
|
||||||
|
* {@link #transactionWithResult(boolean, DbCallable)} and
|
||||||
|
* {@link #transactionWithNullableResult(boolean, NullableDbCallable)} methods
|
||||||
|
* where possible, which handle committing or aborting the transaction on the
|
||||||
|
* caller's behalf.
|
||||||
|
* <p>
|
||||||
|
* Transactions are not reentrant, i.e. it is not permitted to start a
|
||||||
|
* transaction on a thread that already has a transaction in progress.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface TransactionManager {
|
public interface TransactionManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new transaction and returns an object representing it.
|
* Starts a new transaction and returns an object representing it. This
|
||||||
* <p/>
|
* method acquires the database lock, which is held until
|
||||||
* This method acquires locks, so it must not be called while holding a
|
* {@link #endTransaction(Transaction)} is called.
|
||||||
* lock.
|
|
||||||
*
|
*
|
||||||
* @param readOnly true if the transaction will only be used for reading.
|
* @param readOnly True if the transaction will only be used for reading,
|
||||||
|
* in which case the database lock can be shared with other read-only
|
||||||
|
* transactions.
|
||||||
*/
|
*/
|
||||||
Transaction startTransaction(boolean readOnly) throws DbException;
|
Transaction startTransaction(boolean readOnly) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits a transaction to the database.
|
* Commits a transaction to the database.
|
||||||
|
* {@link #endTransaction(Transaction)} must be called to release the
|
||||||
|
* database lock.
|
||||||
*/
|
*/
|
||||||
void commitTransaction(Transaction txn) throws DbException;
|
void commitTransaction(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends a transaction. If the transaction has not been committed,
|
* Ends a transaction. If the transaction has not been committed by
|
||||||
* it will be aborted. If the transaction has been committed,
|
* calling {@link #commitTransaction(Transaction)}, it is aborted and the
|
||||||
* any events attached to the transaction are broadcast.
|
* database lock is released.
|
||||||
* The database lock will be released in either case.
|
* <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.
|
||||||
*/
|
*/
|
||||||
void endTransaction(Transaction txn);
|
void endTransaction(Transaction txn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction.
|
* 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.
|
||||||
*/
|
*/
|
||||||
<E extends Exception> void transaction(boolean readOnly,
|
<E extends Exception> void transaction(boolean readOnly,
|
||||||
DbRunnable<E> task) throws DbException, E;
|
DbRunnable<E> task) throws DbException, E;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction and returns the result of the
|
* Runs the given task within a transaction and returns the result of the
|
||||||
* task.
|
* 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.
|
||||||
*/
|
*/
|
||||||
<R, E extends Exception> R transactionWithResult(boolean readOnly,
|
<R, E extends Exception> R transactionWithResult(boolean readOnly,
|
||||||
DbCallable<R, E> task) throws DbException, E;
|
DbCallable<R, E> task) throws DbException, E;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given task within a transaction and returns the result of the
|
* Runs the given task within a transaction and returns the result of the
|
||||||
* task, which may be null.
|
* 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.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,
|
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.HOURS;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
|
||||||
|
public interface MailboxConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of a file that can be uploaded to or downloaded from
|
||||||
|
* a mailbox.
|
||||||
|
*/
|
||||||
|
int MAX_FILE_BYTES = 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of the plaintext payload of a file, such that the
|
||||||
|
* ciphertext is no more than {@link #MAX_FILE_BYTES}.
|
||||||
|
*/
|
||||||
|
int MAX_FILE_PAYLOAD_BYTES =
|
||||||
|
(MAX_FILE_BYTES - TAG_LENGTH - STREAM_HEADER_LENGTH)
|
||||||
|
/ MAX_FRAME_LENGTH * MAX_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of connection failures
|
||||||
|
* that indicate a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
int PROBLEM_NUM_CONNECTION_FAILURES = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time in milliseconds since the last connection success
|
||||||
|
* that need to pass to indicates a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -41,4 +43,14 @@ public interface MailboxManager {
|
|||||||
*/
|
*/
|
||||||
boolean checkConnection();
|
boolean checkConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpairs the owner's mailbox and tries to wipe it.
|
||||||
|
* As this makes a network call, it should be run on the {@link IoExecutor}.
|
||||||
|
*
|
||||||
|
* @return true if we could wipe the mailbox, false if we couldn't.
|
||||||
|
* It is advised to inform the user to wipe the mailbox themselves,
|
||||||
|
* if we failed to wipe it.
|
||||||
|
*/
|
||||||
|
@IoExecutor
|
||||||
|
boolean unPair() throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -11,12 +14,37 @@ public class MailboxProperties {
|
|||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
private final MailboxAuthToken authToken;
|
private final MailboxAuthToken authToken;
|
||||||
private final boolean owner;
|
private final boolean owner;
|
||||||
|
private final List<MailboxVersion> serverSupports;
|
||||||
|
@Nullable
|
||||||
|
private final MailboxFolderId inboxId; // Null for own mailbox
|
||||||
|
@Nullable
|
||||||
|
private final MailboxFolderId outboxId; // Null for own mailbox
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for properties used by the mailbox's owner.
|
||||||
|
*/
|
||||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||||
boolean owner) {
|
List<MailboxVersion> serverSupports) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
this.owner = owner;
|
this.owner = true;
|
||||||
|
this.serverSupports = serverSupports;
|
||||||
|
this.inboxId = null;
|
||||||
|
this.outboxId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for properties used by a contact of the mailbox's owner.
|
||||||
|
*/
|
||||||
|
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||||
|
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
|
||||||
|
MailboxFolderId outboxId) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.authToken = authToken;
|
||||||
|
this.owner = false;
|
||||||
|
this.serverSupports = serverSupports;
|
||||||
|
this.inboxId = inboxId;
|
||||||
|
this.outboxId = outboxId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
@@ -35,4 +63,18 @@ public class MailboxProperties {
|
|||||||
public boolean isOwner() {
|
public boolean isOwner() {
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MailboxVersion> getServerSupports() {
|
||||||
|
return serverSupports;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MailboxFolderId getInboxId() {
|
||||||
|
return inboxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MailboxFolderId getOutboxId() {
|
||||||
|
return outboxId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -26,6 +28,8 @@ public interface MailboxSettingsManager {
|
|||||||
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
void removeOwnMailboxProperties(Transaction txn) throws DbException;
|
||||||
|
|
||||||
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||||
|
|
||||||
void recordSuccessfulConnection(Transaction txn, long now)
|
void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
@@ -47,7 +51,8 @@ public interface MailboxSettingsManager {
|
|||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
||||||
*/
|
*/
|
||||||
void mailboxPaired(Transaction txn, String ownOnion)
|
void mailboxPaired(Transaction txn, String ownOnion,
|
||||||
|
List<MailboxVersion> serverSupports)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxStatus {
|
public class MailboxStatus {
|
||||||
@@ -56,4 +59,12 @@ public class MailboxStatus {
|
|||||||
public int getAttemptsSinceSuccess() {
|
public int getAttemptsSinceSuccess() {
|
||||||
return attemptsSinceSuccess;
|
return attemptsSinceSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this status indicates a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
public boolean hasProblem(long now) {
|
||||||
|
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
|
||||||
|
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,31 +9,31 @@ import org.briarproject.bramble.api.sync.ClientId;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface MailboxPropertyManager {
|
public interface MailboxUpdateManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unique ID of the mailbox property client.
|
* The unique ID of the mailbox update (properties) client.
|
||||||
*/
|
*/
|
||||||
ClientId CLIENT_ID =
|
ClientId CLIENT_ID =
|
||||||
new ClientId("org.briarproject.bramble.mailbox.properties");
|
new ClientId("org.briarproject.bramble.mailbox.properties");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current major version of the mailbox property client.
|
* The current major version of the mailbox update (properties) client.
|
||||||
*/
|
*/
|
||||||
int MAJOR_VERSION = 0;
|
int MAJOR_VERSION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current minor version of the mailbox property client.
|
* The current minor version of the mailbox update (properties) client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 0;
|
int MINOR_VERSION = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of properties required for a (non-empty) update message.
|
* The number of properties required for an update message with a mailbox.
|
||||||
*/
|
*/
|
||||||
int PROP_COUNT = 4;
|
int PROP_COUNT = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The required properties of a non-empty update message.
|
* The required properties of an update message with a mailbox.
|
||||||
*/
|
*/
|
||||||
String PROP_KEY_ONION = "onion";
|
String PROP_KEY_ONION = "onion";
|
||||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||||
@@ -57,11 +57,16 @@ public interface MailboxPropertyManager {
|
|||||||
*/
|
*/
|
||||||
String MSG_KEY_LOCAL = "local";
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
|
||||||
@Nullable
|
/**
|
||||||
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
|
* Key in the client's local group for storing the clientSupports list that
|
||||||
|
* was last sent out.
|
||||||
|
*/
|
||||||
|
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
|
||||||
|
|
||||||
|
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c)
|
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxVersion implements Comparable<MailboxVersion> {
|
||||||
|
|
||||||
|
private final int major;
|
||||||
|
private final int minor;
|
||||||
|
|
||||||
|
public MailboxVersion(int major, int minor) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMajor() {
|
||||||
|
return major;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinor() {
|
||||||
|
return minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof MailboxVersion) {
|
||||||
|
MailboxVersion v = (MailboxVersion) o;
|
||||||
|
return major == v.major && minor == v.minor;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(MailboxVersion v) {
|
||||||
|
int c = major - v.major;
|
||||||
|
if (c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return minor - v.minor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.mailbox;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast by {@link MailboxSettingsManager} when
|
||||||
|
* recording a connection failure for own Mailbox
|
||||||
|
* that has persistent for long enough for the mailbox owner to become active
|
||||||
|
* and fix the problem with the mailbox.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxProblemEvent extends Event {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.bramble.api.mailbox;
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when {@link MailboxUpdate} are received
|
||||||
|
* from a contact.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class RemoteMailboxUpdateEvent extends Event {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final MailboxUpdate mailboxUpdate;
|
||||||
|
|
||||||
|
public RemoteMailboxUpdateEvent(ContactId contactId,
|
||||||
|
MailboxUpdate mailboxUpdate) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.mailboxUpdate = mailboxUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContact() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxUpdate getMailboxUpdate() {
|
||||||
|
return mailboxUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
public interface TorConstants {
|
public interface TorConstants {
|
||||||
|
|
||||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||||
@@ -10,8 +12,9 @@ public interface TorConstants {
|
|||||||
int DEFAULT_SOCKS_PORT = 59050;
|
int DEFAULT_SOCKS_PORT = 59050;
|
||||||
int DEFAULT_CONTROL_PORT = 59051;
|
int DEFAULT_CONTROL_PORT = 59051;
|
||||||
|
|
||||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
|
||||||
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
|
||||||
|
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
|
||||||
|
|
||||||
// Local settings (not shared with contacts)
|
// Local settings (not shared with contacts)
|
||||||
String PREF_TOR_NETWORK = "network2";
|
String PREF_TOR_NETWORK = "network2";
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ public interface RecordWriter {
|
|||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
|
|
||||||
void close() throws IOException;
|
void close() throws IOException;
|
||||||
|
|
||||||
|
long getBytesWritten();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -20,4 +20,6 @@ public interface SyncRecordWriter {
|
|||||||
void writePriority(Priority p) throws IOException;
|
void writePriority(Priority p) throws IOException;
|
||||||
|
|
||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
|
|
||||||
|
long getBytesWritten();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.api.system;
|
package org.briarproject.bramble.api.system;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -16,6 +17,8 @@ public interface TaskScheduler {
|
|||||||
* <p>
|
* <p>
|
||||||
* If the platform supports wake locks, a wake lock will be held while
|
* If the platform supports wake locks, a wake lock will be held while
|
||||||
* submitting and running the task.
|
* submitting and running the task.
|
||||||
|
*
|
||||||
|
* @return A {@link Cancellable} for cancelling the task.
|
||||||
*/
|
*/
|
||||||
Cancellable schedule(Runnable task, Executor executor, long delay,
|
Cancellable schedule(Runnable task, Executor executor, long delay,
|
||||||
TimeUnit unit);
|
TimeUnit unit);
|
||||||
@@ -27,17 +30,11 @@ public interface TaskScheduler {
|
|||||||
* <p>
|
* <p>
|
||||||
* If the platform supports wake locks, a wake lock will be held while
|
* If the platform supports wake locks, a wake lock will be held while
|
||||||
* submitting and running the task.
|
* submitting and running the task.
|
||||||
|
*
|
||||||
|
* @return A {@link Cancellable} for cancelling all future executions of
|
||||||
|
* the task.
|
||||||
*/
|
*/
|
||||||
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
||||||
long delay, long interval, TimeUnit unit);
|
long delay, long interval, TimeUnit unit);
|
||||||
|
|
||||||
interface Cancellable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the task if it has not already started running. If the task
|
|
||||||
* is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long, TimeUnit) periodic},
|
|
||||||
* all future executions of the task are cancelled.
|
|
||||||
*/
|
|
||||||
void cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
@@ -221,6 +226,19 @@ public class TestUtils {
|
|||||||
getAgreementPublicKey(), verified);
|
getAgreementPublicKey(), verified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MailboxProperties getMailboxProperties(boolean owner,
|
||||||
|
List<MailboxVersion> serverSupports) {
|
||||||
|
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
|
||||||
|
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
||||||
|
if (owner) {
|
||||||
|
return new MailboxProperties(baseUrl, authToken, serverSupports);
|
||||||
|
}
|
||||||
|
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
||||||
|
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
||||||
|
return new MailboxProperties(baseUrl, authToken, serverSupports,
|
||||||
|
inboxId, outboxId);
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeBytes(File file, byte[] bytes)
|
public static void writeBytes(File file, byte[] bytes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
FileOutputStream outputStream = new FileOutputStream(file);
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
@@ -273,22 +291,38 @@ public class TestUtils {
|
|||||||
return Math.sqrt(getVariance(samples));
|
return Math.sqrt(getVariance(samples));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOptionalTestEnabled(Class testClass) {
|
public static boolean isOptionalTestEnabled(Class<?> testClass) {
|
||||||
String optionalTests = System.getenv("OPTIONAL_TESTS");
|
String optionalTests = System.getenv("OPTIONAL_TESTS");
|
||||||
return optionalTests != null &&
|
return optionalTests != null &&
|
||||||
asList(optionalTests.split(",")).contains(testClass.getName());
|
asList(optionalTests.split(",")).contains(testClass.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean mailboxPropertiesUpdateEqual(
|
public static boolean mailboxUpdateEqual(@Nullable MailboxUpdate a,
|
||||||
@Nullable MailboxPropertiesUpdate a,
|
@Nullable MailboxUpdate b) {
|
||||||
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean mailboxPropertiesEqual(@Nullable MailboxProperties a,
|
||||||
|
@Nullable MailboxProperties b) {
|
||||||
if (a == null || b == null) {
|
if (a == null || b == null) {
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
return a.getOnion().equals(b.getOnion()) &&
|
return a.getOnion().equals(b.getOnion()) &&
|
||||||
a.getAuthToken().equals(b.getAuthToken()) &&
|
a.getAuthToken().equals(b.getAuthToken()) &&
|
||||||
a.getInboxId().equals(b.getInboxId()) &&
|
a.isOwner() == b.isOwner() &&
|
||||||
a.getOutboxId().equals(b.getOutboxId());
|
a.getServerSupports().equals(b.getServerSupports());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasEvent(Transaction txn,
|
public static boolean hasEvent(Transaction txn,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ dependencies {
|
|||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||||
implementation 'org.briarproject:jtorctl:0.3'
|
implementation 'org.briarproject:jtorctl:0.4'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -39,12 +42,13 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -52,12 +56,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
|
|||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_ONION_LENGTH;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_ONION_LENGTH;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
@@ -412,11 +416,28 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
public MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
|
||||||
public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
BdfList serverSupports, BdfDictionary properties)
|
||||||
BdfDictionary properties) throws FormatException {
|
throws FormatException {
|
||||||
|
List<MailboxVersion> clientSupportsList =
|
||||||
|
parseMailboxVersionList(clientSupports);
|
||||||
|
List<MailboxVersion> serverSupportsList =
|
||||||
|
parseMailboxVersionList(serverSupports);
|
||||||
|
|
||||||
|
// We must always learn what Mailbox API version(s) the client supports
|
||||||
|
if (clientSupports.isEmpty()) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
if (properties.isEmpty()) {
|
if (properties.isEmpty()) {
|
||||||
return null;
|
// No mailbox -- cannot claim to support any API versions!
|
||||||
|
if (!serverSupports.isEmpty()) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
return new MailboxUpdate(clientSupportsList);
|
||||||
|
}
|
||||||
|
// Mailbox must be accompanied by the Mailbox API version(s) it supports
|
||||||
|
if (serverSupports.isEmpty()) {
|
||||||
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
// Accepting more props than we need, for forward compatibility
|
// Accepting more props than we need, for forward compatibility
|
||||||
if (properties.size() < PROP_COUNT) {
|
if (properties.size() < PROP_COUNT) {
|
||||||
@@ -435,9 +456,26 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
checkLength(inboxId, UniqueId.LENGTH);
|
checkLength(inboxId, UniqueId.LENGTH);
|
||||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||||
checkLength(outboxId, UniqueId.LENGTH);
|
checkLength(outboxId, UniqueId.LENGTH);
|
||||||
return new MailboxPropertiesUpdate(onion,
|
String baseUrl = "http://" + onion + ".onion"; // TODO
|
||||||
new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
|
MailboxProperties props = new MailboxProperties(baseUrl,
|
||||||
new MailboxFolderId(outboxId));
|
new MailboxAuthToken(authToken), serverSupportsList,
|
||||||
|
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
|
||||||
|
return new MailboxUpdateWithMailbox(clientSupportsList, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
|
||||||
|
throws FormatException {
|
||||||
|
List<MailboxVersion> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < bdfList.size(); i++) {
|
||||||
|
BdfList element = bdfList.getList(i);
|
||||||
|
if (element.size() != 2) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
list.add(new MailboxVersion(element.getLong(0).intValue(),
|
||||||
|
element.getLong(1).intValue()));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -406,6 +406,12 @@ interface Database<T> {
|
|||||||
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
|
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the given message in bytes, including the
|
||||||
|
* message header.
|
||||||
|
*/
|
||||||
|
int getMessageLength(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the metadata for all delivered messages in the given group.
|
* Returns the metadata for all delivered messages in the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -496,7 +502,8 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact, up to the given total length.
|
* given contact. The total length of the messages including record headers
|
||||||
|
* will be no more than the given capacity.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
||||||
* does not return messages that have already been sent unless they are
|
* does not return messages that have already been sent unless they are
|
||||||
@@ -504,20 +511,20 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity,
|
||||||
long maxLatency) throws DbException;
|
long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all messages that are eligible to be sent to the
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
* given contact, together with their raw lengths.
|
* given contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
|
* Unlike {@link #getMessagesToSend(Object, ContactId, long, long)} this
|
||||||
* method may return messages that have already been sent and are not yet
|
* method may return messages that have already been sent and are not yet
|
||||||
* due for retransmission.
|
* due for retransmission.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
|
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -598,13 +605,14 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact and have been requested by the contact, up to the given
|
* given contact and have been requested by the contact. The total length
|
||||||
* total length.
|
* of the messages including record headers will be no more than the given
|
||||||
|
* capacity.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
|
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
|
||||||
int maxLength, long maxLatency) throws DbException;
|
long capacity, long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all settings in the given namespace.
|
* Returns all settings in the given namespace.
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ import org.briarproject.bramble.api.transport.TransportKeys;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -87,6 +86,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -424,13 +424,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Collection<Message> generateBatch(Transaction transaction,
|
public Collection<Message> generateBatch(Transaction transaction,
|
||||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getMessagesToSend(txn, c, capacity, maxLatency);
|
||||||
|
if (ids.isEmpty()) return null;
|
||||||
long totalLength = 0;
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
@@ -439,38 +440,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Message> generateBatch(Transaction transaction,
|
|
||||||
ContactId c, Collection<MessageId> ids, long maxLatency)
|
|
||||||
throws DbException {
|
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
long totalLength = 0;
|
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
|
||||||
List<MessageId> sentIds = new ArrayList<>(ids.size());
|
|
||||||
for (MessageId m : ids) {
|
|
||||||
if (db.containsVisibleMessage(txn, c, m)) {
|
|
||||||
Message message = db.getMessage(txn, m);
|
|
||||||
totalLength += message.getRawLength();
|
|
||||||
messages.add(message);
|
|
||||||
sentIds.add(m);
|
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (messages.isEmpty()) return messages;
|
|
||||||
db.lowerRequestedFlag(txn, c, sentIds);
|
|
||||||
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Offer generateOffer(Transaction transaction, ContactId c,
|
public Offer generateOffer(Transaction transaction, ContactId c,
|
||||||
@@ -505,13 +479,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Collection<Message> generateRequestedBatch(Transaction transaction,
|
public Collection<Message> generateRequestedBatch(Transaction transaction,
|
||||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency);
|
||||||
|
if (ids.isEmpty()) return null;
|
||||||
long totalLength = 0;
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
@@ -520,7 +495,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
@@ -635,6 +609,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageIds(txn, g, query);
|
return db.getMessageIds(txn, g, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
||||||
|
ContactId c, int maxMessages) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getMessagesToAck(txn, c, maxMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MessageId> getMessagesToSend(Transaction transaction,
|
||||||
|
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getMessagesToSend(txn, c, capacity, maxLatency);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -740,10 +732,29 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, Integer> getUnackedMessagesToSend(
|
public Message getMessageToSend(Transaction transaction, ContactId c,
|
||||||
Transaction transaction,
|
MessageId m, long maxLatency, boolean markAsSent)
|
||||||
ContactId c) throws DbException {
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
if (!db.containsVisibleMessage(txn, c, m)) return null;
|
||||||
|
Message message = db.getMessage(txn, m);
|
||||||
|
if (markAsSent) {
|
||||||
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
|
db.lowerRequestedFlag(txn, c, singletonList(m));
|
||||||
|
transaction.attach(new MessagesSentEvent(c, singletonList(m),
|
||||||
|
message.getRawLength()));
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MessageId> getUnackedMessagesToSend(
|
||||||
|
Transaction transaction, ContactId c) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
@@ -1069,6 +1080,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.removeTransportKeys(txn, t, k);
|
db.removeTransportKeys(txn, t, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAckSent(Transaction transaction, ContactId c,
|
||||||
|
Collection<MessageId> acked) throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
List<MessageId> visible = new ArrayList<>(acked.size());
|
||||||
|
for (MessageId m : acked) {
|
||||||
|
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
|
||||||
|
}
|
||||||
|
db.lowerAckFlag(txn, c, visible);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
||||||
long duration) throws DbException {
|
long duration) throws DbException {
|
||||||
@@ -1115,7 +1140,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
List<ContactId> affected = Collections.singletonList(c);
|
List<ContactId> affected = singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1163,6 +1188,28 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new MessageStateChangedEvent(m, false, state));
|
transaction.attach(new MessageStateChangedEvent(m, false, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessagesSent(Transaction transaction, ContactId c,
|
||||||
|
Collection<MessageId> sent, long maxLatency) throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
long totalLength = 0;
|
||||||
|
List<MessageId> visible = new ArrayList<>(sent.size());
|
||||||
|
for (MessageId m : sent) {
|
||||||
|
if (db.containsVisibleMessage(txn, c, m)) {
|
||||||
|
visible.add(m);
|
||||||
|
totalLength += db.getMessageLength(txn, m);
|
||||||
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.lowerRequestedFlag(txn, c, visible);
|
||||||
|
if (!visible.isEmpty()) {
|
||||||
|
transaction.attach(new MessagesSentEvent(c, visible, totalLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessageDependencies(Transaction transaction,
|
public void addMessageDependencies(Transaction transaction,
|
||||||
Message dependent, Collection<MessageId> dependencies)
|
Message dependent, Collection<MessageId> dependencies)
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -70,12 +69,14 @@ import static java.sql.Types.BOOLEAN;
|
|||||||
import static java.sql.Types.INTEGER;
|
import static java.sql.Types.INTEGER;
|
||||||
import static java.sql.Types.VARCHAR;
|
import static java.sql.Types.VARCHAR;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.logging.Level.FINE;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -102,6 +103,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 50;
|
static final int CODE_SCHEMA_VERSION = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of idle connections to keep open.
|
||||||
|
*/
|
||||||
|
private static final int MAX_CONNECTION_POOL_SIZE = 1;
|
||||||
|
|
||||||
// Time period offsets for incoming transport keys
|
// Time period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
private static final int OFFSET_CURR = 0;
|
private static final int OFFSET_CURR = 0;
|
||||||
@@ -363,7 +369,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
@GuardedBy("connectionsLock")
|
@GuardedBy("connectionsLock")
|
||||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
private final LinkedList<Connection> connectionPool = new LinkedList<>();
|
||||||
|
|
||||||
@GuardedBy("connectionsLock")
|
@GuardedBy("connectionsLock")
|
||||||
private int openConnections = 0;
|
private int openConnections = 0;
|
||||||
@@ -571,7 +577,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) throw new DbClosedException();
|
if (closed) throw new DbClosedException();
|
||||||
txn = connections.poll();
|
txn = connectionPool.poll();
|
||||||
|
logConnectionCounts();
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -582,7 +589,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
txn.setAutoCommit(false);
|
txn.setAutoCommit(false);
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
|
// The DB may have been closed since the check above
|
||||||
|
if (closed) {
|
||||||
|
tryToClose(txn, LOG, WARNING);
|
||||||
|
throw new DbClosedException();
|
||||||
|
}
|
||||||
openConnections++;
|
openConnections++;
|
||||||
|
logConnectionCounts();
|
||||||
|
connectionsChanged.signalAll();
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -593,67 +607,91 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
return txn;
|
return txn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@GuardedBy("connectionsLock")
|
||||||
public void abortTransaction(Connection txn) {
|
private void logConnectionCounts() {
|
||||||
try {
|
if (LOG.isLoggable(FINE)) {
|
||||||
txn.rollback();
|
LOG.fine(openConnections + " connections open, "
|
||||||
connectionsLock.lock();
|
+ connectionPool.size() + " in pool");
|
||||||
try {
|
|
||||||
connections.add(txn);
|
|
||||||
connectionsChanged.signalAll();
|
|
||||||
} finally {
|
|
||||||
connectionsLock.unlock();
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
// Try to close the connection
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
tryToClose(txn, LOG, WARNING);
|
|
||||||
// Whatever happens, allow the database to close
|
|
||||||
connectionsLock.lock();
|
|
||||||
try {
|
|
||||||
openConnections--;
|
|
||||||
connectionsChanged.signalAll();
|
|
||||||
} finally {
|
|
||||||
connectionsLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commitTransaction(Connection txn) throws DbException {
|
public void abortTransaction(Connection txn) {
|
||||||
|
// The transaction may have been aborted due to an earlier exception,
|
||||||
|
// so close the connection rather than returning it to the pool
|
||||||
try {
|
try {
|
||||||
txn.commit();
|
txn.rollback();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DbException(e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
|
closeConnection(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeConnection(Connection txn) {
|
||||||
|
tryToClose(txn, LOG, WARNING);
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
connections.add(txn);
|
openConnections--;
|
||||||
|
logConnectionCounts();
|
||||||
connectionsChanged.signalAll();
|
connectionsChanged.signalAll();
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeAllConnections() throws SQLException {
|
@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();
|
||||||
|
connectionsChanged.signalAll();
|
||||||
|
} finally {
|
||||||
|
connectionsLock.unlock();
|
||||||
|
}
|
||||||
|
if (shouldClose) tryToClose(txn, LOG, WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeAllConnections() {
|
||||||
boolean interrupted = false;
|
boolean interrupted = false;
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
closed = true;
|
closed = true;
|
||||||
for (Connection c : connections) c.close();
|
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
||||||
openConnections -= connections.size();
|
openConnections -= connectionPool.size();
|
||||||
connections.clear();
|
connectionPool.clear();
|
||||||
while (openConnections > 0) {
|
while (openConnections > 0) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Waiting for " + openConnections
|
||||||
|
+ " connections to be closed");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
connectionsChanged.await();
|
connectionsChanged.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.warning("Interrupted while closing connections");
|
LOG.warning("Interrupted while closing connections");
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
}
|
}
|
||||||
for (Connection c : connections) c.close();
|
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
|
||||||
openConnections -= connections.size();
|
openConnections -= connectionPool.size();
|
||||||
connections.clear();
|
connectionPool.clear();
|
||||||
}
|
}
|
||||||
|
LOG.info("All connections closed");
|
||||||
} finally {
|
} finally {
|
||||||
connectionsLock.unlock();
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
@@ -1879,6 +1917,31 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMessageLength(Connection txn, MessageId m)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT length from messages"
|
||||||
|
+ " WHERE messageId = ? AND state = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
int length = rs.getInt(1);
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return length;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
|
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
|
||||||
GroupId g) throws DbException {
|
GroupId g) throws DbException {
|
||||||
@@ -2227,8 +2290,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
public Collection<MessageId> getMessagesToSend(Connection txn,
|
||||||
int maxLength, long maxLatency) throws DbException {
|
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2248,12 +2311,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setLong(4, maxLatency);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
int total = 0;
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
int length = rs.getInt(1);
|
int length = rs.getInt(1);
|
||||||
if (total + length > maxLength) break;
|
if (capacity < RECORD_HEADER_BYTES + length) break;
|
||||||
ids.add(new MessageId(rs.getBytes(2)));
|
ids.add(new MessageId(rs.getBytes(2)));
|
||||||
total += length;
|
capacity -= RECORD_HEADER_BYTES + length;
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2266,12 +2328,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
|
public Collection<MessageId> getUnackedMessagesToSend(Connection txn,
|
||||||
ContactId c) throws DbException {
|
ContactId c) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT length, messageId FROM statuses"
|
String sql = "SELECT messageId FROM statuses"
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
@@ -2280,15 +2342,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
Map<MessageId, Integer> results = new LinkedHashMap<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
while (rs.next()) {
|
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||||
int length = rs.getInt(1);
|
|
||||||
MessageId id = new MessageId(rs.getBytes(2));
|
|
||||||
results.put(id, length);
|
|
||||||
}
|
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
return results;
|
return ids;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps, LOG, WARNING);
|
tryToClose(ps, LOG, WARNING);
|
||||||
@@ -2401,6 +2459,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
MessageId m = new MessageId(rs.getBytes(1));
|
MessageId m = new MessageId(rs.getBytes(1));
|
||||||
GroupId g = new GroupId(rs.getBytes(2));
|
GroupId g = new GroupId(rs.getBytes(2));
|
||||||
Collection<MessageId> messageIds = ids.get(g);
|
Collection<MessageId> messageIds = ids.get(g);
|
||||||
|
//noinspection Java8MapApi
|
||||||
if (messageIds == null) {
|
if (messageIds == null) {
|
||||||
messageIds = new ArrayList<>();
|
messageIds = new ArrayList<>();
|
||||||
ids.put(g, messageIds);
|
ids.put(g, messageIds);
|
||||||
@@ -2527,7 +2586,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
||||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
ContactId c, long capacity, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
@@ -2547,12 +2606,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setLong(4, maxLatency);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
int total = 0;
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
int length = rs.getInt(1);
|
int length = rs.getInt(1);
|
||||||
if (total + length > maxLength) break;
|
if (capacity < RECORD_HEADER_BYTES + length) break;
|
||||||
ids.add(new MessageId(rs.getBytes(2)));
|
ids.add(new MessageId(rs.getBytes(2)));
|
||||||
total += length;
|
capacity -= RECORD_HEADER_BYTES + length;
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2706,6 +2764,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ContactId c = new ContactId(rs.getInt(1));
|
ContactId c = new ContactId(rs.getInt(1));
|
||||||
TransportId t = new TransportId(rs.getString(2));
|
TransportId t = new TransportId(rs.getString(2));
|
||||||
Collection<TransportId> transportIds = ids.get(c);
|
Collection<TransportId> transportIds = ids.get(c);
|
||||||
|
//noinspection Java8MapApi
|
||||||
if (transportIds == null) {
|
if (transportIds == null) {
|
||||||
transportIds = new ArrayList<>();
|
transportIds = new ArrayList<>();
|
||||||
ids.put(c, transportIds);
|
ids.put(c, transportIds);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.briarproject.bramble.io;
|
package org.briarproject.bramble.io;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.system.Wakeful;
|
import org.briarproject.bramble.api.system.Wakeful;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for checking whether a mailbox is reachable.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface ConnectivityChecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the checker. Any current connectivity check is cancelled.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a connectivity check if needed and calls the given observer when
|
||||||
|
* the check succeeds. If a check is already running then the observer is
|
||||||
|
* called when the check succeeds. If a connectivity check has recently
|
||||||
|
* succeeded then the observer is called immediately.
|
||||||
|
*/
|
||||||
|
void checkConnectivity(MailboxProperties properties,
|
||||||
|
ConnectivityObserver o);
|
||||||
|
|
||||||
|
interface ConnectivityObserver {
|
||||||
|
void onConnectivityCheckSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||||
|
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
|
||||||
|
ContactMailboxConnectivityChecker(Clock clock,
|
||||||
|
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
||||||
|
super(clock, mailboxApiCaller);
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||||
|
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
return new SimpleApiCall() {
|
||||||
|
@Override
|
||||||
|
void tryToCallApi() throws IOException, ApiException {
|
||||||
|
if (!mailboxApi.checkStatus(properties)) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
// Call the observers and cache the result
|
||||||
|
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -16,8 +19,21 @@ import java.util.List;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
interface MailboxApi {
|
interface MailboxApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mailbox API versions that we support as a client. This is reported to our
|
||||||
|
* contacts by {@link MailboxUpdateManager}.
|
||||||
|
*/
|
||||||
|
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
|
||||||
|
new MailboxVersion(1, 0));
|
||||||
|
|
||||||
|
List<MailboxVersion> getServerSupports(MailboxProperties properties)
|
||||||
|
throws IOException, ApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the mailbox with the setup token.
|
* Sets up the mailbox with the setup token.
|
||||||
*
|
*
|
||||||
@@ -25,7 +41,7 @@ interface MailboxApi {
|
|||||||
* @return the owner token
|
* @return the owner token
|
||||||
* @throws ApiException for 401 response.
|
* @throws ApiException for 401 response.
|
||||||
*/
|
*/
|
||||||
MailboxAuthToken setup(MailboxProperties properties)
|
MailboxProperties setup(MailboxProperties properties)
|
||||||
throws IOException, ApiException;
|
throws IOException, ApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxApiCaller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum interval between retries in milliseconds.
|
||||||
|
*/
|
||||||
|
long MIN_RETRY_INTERVAL_MS = MINUTES.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum interval between retries in milliseconds.
|
||||||
|
*/
|
||||||
|
long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously calls the given API call on the {@link IoExecutor},
|
||||||
|
* automatically retrying at increasing intervals until the API call
|
||||||
|
* returns false or retries are cancelled.
|
||||||
|
*
|
||||||
|
* @return A {@link Cancellable} that can be used to cancel any future
|
||||||
|
* retries.
|
||||||
|
*/
|
||||||
|
Cancellable retryWithBackoff(ApiCall apiCall);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxId;
|
import org.briarproject.bramble.api.mailbox.MailboxId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -56,7 +57,24 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MailboxAuthToken setup(MailboxProperties properties)
|
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 {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
Request request = getRequestBuilder(properties.getAuthToken())
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
@@ -75,17 +93,40 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
if (tokenNode == null) {
|
if (tokenNode == null) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
}
|
}
|
||||||
String ownerToken = tokenNode.textValue();
|
return new MailboxProperties(properties.getBaseUrl(),
|
||||||
return MailboxAuthToken.fromString(ownerToken);
|
MailboxAuthToken.fromString(tokenNode.textValue()),
|
||||||
|
parseServerSupports(node));
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<MailboxVersion> parseServerSupports(JsonNode node)
|
||||||
|
throws ApiException {
|
||||||
|
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||||
|
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
|
||||||
|
for (JsonNode versionNode : serverSupportsNode) {
|
||||||
|
if (!versionNode.isObject()) throw new ApiException();
|
||||||
|
ObjectNode objectNode = (ObjectNode) versionNode;
|
||||||
|
JsonNode majorNode = objectNode.get("major");
|
||||||
|
JsonNode minorNode = objectNode.get("minor");
|
||||||
|
if (majorNode == null || !majorNode.isNumber()) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
if (minorNode == null || !minorNode.isNumber()) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
int major = majorNode.asInt();
|
||||||
|
int minor = minorNode.asInt();
|
||||||
|
if (major < 0 || minor < 0) throw new ApiException();
|
||||||
|
serverSupports.add(new MailboxVersion(major, minor));
|
||||||
|
}
|
||||||
|
return serverSupports;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkStatus(MailboxProperties properties)
|
public boolean checkStatus(MailboxProperties properties)
|
||||||
throws IOException, ApiException {
|
throws IOException, ApiException {
|
||||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
|
||||||
Response response = sendGetRequest(properties, "/status");
|
Response response = sendGetRequest(properties, "/status");
|
||||||
if (response.code() == 401) throw new ApiException();
|
if (response.code() == 401) throw new ApiException();
|
||||||
return response.isSuccessful();
|
return response.isSuccessful();
|
||||||
|
|||||||
@@ -102,23 +102,55 @@ class MailboxManagerImpl implements MailboxManager {
|
|||||||
try {
|
try {
|
||||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||||
mailboxSettingsManager::getOwnMailboxProperties);
|
mailboxSettingsManager::getOwnMailboxProperties);
|
||||||
|
if (props == null) throw new DbException();
|
||||||
success = api.checkStatus(props);
|
success = api.checkStatus(props);
|
||||||
} catch (DbException | IOException | MailboxApi.ApiException e) {
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// we don't treat this is a failure to record
|
||||||
|
return false;
|
||||||
|
} catch (IOException | MailboxApi.ApiException e) {
|
||||||
|
// we record this as a failure
|
||||||
success = false;
|
success = false;
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
if (success) {
|
try {
|
||||||
try {
|
recordCheckResult(success);
|
||||||
// we are only recording successful connections here
|
} catch (DbException e) {
|
||||||
// as those update the UI and failures might be false negatives
|
logException(LOG, WARNING, e);
|
||||||
db.transaction(false, txn ->
|
|
||||||
mailboxSettingsManager.recordSuccessfulConnection(txn,
|
|
||||||
clock.currentTimeMillis()));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return success;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,39 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MailboxModule {
|
public class MailboxModule {
|
||||||
|
|
||||||
public static class EagerSingletons {
|
public static class EagerSingletons {
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPropertyValidator mailboxPropertyValidator;
|
MailboxUpdateValidator mailboxUpdateValidator;
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPropertyManager mailboxPropertyManager;
|
MailboxUpdateManager mailboxUpdateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -56,31 +61,44 @@ public class MailboxModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
MailboxPropertyValidator provideMailboxPropertyValidator(
|
MailboxUpdateValidator provideMailboxUpdateValidator(
|
||||||
ValidationManager validationManager, ClientHelper clientHelper,
|
ValidationManager validationManager,
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
ClientHelper clientHelper,
|
||||||
MailboxPropertyValidator validator = new MailboxPropertyValidator(
|
MetadataEncoder metadataEncoder,
|
||||||
|
Clock clock,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
|
MailboxUpdateValidator validator = new MailboxUpdateValidator(
|
||||||
clientHelper, metadataEncoder, clock);
|
clientHelper, metadataEncoder, clock);
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
if (featureFlags.shouldEnableMailbox()) {
|
||||||
validator);
|
validationManager.registerMessageValidator(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, validator);
|
||||||
|
}
|
||||||
return validator;
|
return validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
List<MailboxVersion> provideClientSupports() {
|
||||||
|
return CLIENT_SUPPORTS;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
MailboxPropertyManager provideMailboxPropertyManager(
|
MailboxUpdateManager provideMailboxUpdateManager(
|
||||||
|
FeatureFlags featureFlags,
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
ValidationManager validationManager, ContactManager contactManager,
|
ValidationManager validationManager, ContactManager contactManager,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxPropertyManagerImpl mailboxPropertyManager) {
|
MailboxUpdateManagerImpl mailboxUpdateManager) {
|
||||||
lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager);
|
if (featureFlags.shouldEnableMailbox()) {
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
|
||||||
mailboxPropertyManager);
|
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||||
contactManager.registerContactHook(mailboxPropertyManager);
|
MAJOR_VERSION, mailboxUpdateManager);
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
contactManager.registerContactHook(mailboxUpdateManager);
|
||||||
MINOR_VERSION, mailboxPropertyManager);
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager);
|
MINOR_VERSION, mailboxUpdateManager);
|
||||||
return mailboxPropertyManager;
|
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
|
||||||
|
}
|
||||||
|
return mailboxUpdateManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final MailboxApi api;
|
private final MailboxApi api;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
private final MailboxPropertyManager mailboxPropertyManager;
|
private final MailboxUpdateManager mailboxUpdateManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPairingTaskFactoryImpl(
|
MailboxPairingTaskFactoryImpl(
|
||||||
@@ -34,20 +34,20 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
MailboxApi api,
|
MailboxApi api,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxPropertyManager mailboxPropertyManager) {
|
MailboxUpdateManager mailboxUpdateManager) {
|
||||||
this.eventExecutor = eventExecutor;
|
this.eventExecutor = eventExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
||||||
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
||||||
crypto, clock, api, mailboxSettingsManager,
|
crypto, clock, api, mailboxSettingsManager,
|
||||||
mailboxPropertyManager);
|
mailboxUpdateManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
@@ -51,7 +51,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final MailboxApi api;
|
private final MailboxApi api;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
private final MailboxPropertyManager mailboxPropertyManager;
|
private final MailboxUpdateManager mailboxUpdateManager;
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
@@ -68,7 +68,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
MailboxApi api,
|
MailboxApi api,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxPropertyManager mailboxPropertyManager) {
|
MailboxUpdateManager mailboxUpdateManager) {
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.eventExecutor = eventExecutor;
|
this.eventExecutor = eventExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -76,7 +76,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||||
state = new MailboxPairingState.QrCodeReceived();
|
state = new MailboxPairingState.QrCodeReceived();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +115,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
private void pairMailbox() throws IOException, ApiException, DbException {
|
private void pairMailbox() throws IOException, ApiException, DbException {
|
||||||
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
|
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
|
||||||
setState(new MailboxPairingState.Pairing());
|
setState(new MailboxPairingState.Pairing());
|
||||||
MailboxAuthToken ownerToken = api.setup(mailboxProperties);
|
MailboxProperties ownerProperties = api.setup(mailboxProperties);
|
||||||
MailboxProperties ownerProperties = new MailboxProperties(
|
|
||||||
mailboxProperties.getBaseUrl(), ownerToken, true);
|
|
||||||
long time = clock.currentTimeMillis();
|
long time = clock.currentTimeMillis();
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
mailboxSettingsManager
|
mailboxSettingsManager
|
||||||
@@ -127,9 +125,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
// timers for contacts who doesn't have their own mailbox. This way,
|
// timers for contacts who doesn't have their own mailbox. This way,
|
||||||
// data stranded on our old mailbox will be re-uploaded to our new.
|
// data stranded on our old mailbox will be re-uploaded to our new.
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
MailboxPropertiesUpdate remoteProps = mailboxPropertyManager
|
MailboxUpdate update = mailboxUpdateManager.getRemoteUpdate(
|
||||||
.getRemoteProperties(txn, c.getId());
|
txn, c.getId());
|
||||||
if (remoteProps == null) {
|
if (update == null || !update.hasMailbox()) {
|
||||||
db.resetUnackedMessagesToSend(txn, c.getId());
|
db.resetUnackedMessagesToSend(txn, c.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,10 +177,10 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
|||||||
LOG.info("QR code is valid");
|
LOG.info("QR code is valid");
|
||||||
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||||
String onion = crypto.encodeOnion(onionPubKey);
|
String onion = crypto.encodeOnion(onionPubKey);
|
||||||
String baseUrl = "http://" + onion + ".onion";
|
String baseUrl = "http://" + onion + ".onion"; // TODO
|
||||||
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
||||||
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
||||||
return new MailboxProperties(baseUrl, setupToken, true);
|
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.MailboxProblemEvent;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
@@ -28,8 +31,10 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final String SETTINGS_NAMESPACE = "mailbox";
|
static final String SETTINGS_NAMESPACE = "mailbox";
|
||||||
|
// TODO: This currently stores the base URL, not the 56-char onion address
|
||||||
static final String SETTINGS_KEY_ONION = "onion";
|
static final String SETTINGS_KEY_ONION = "onion";
|
||||||
static final String SETTINGS_KEY_TOKEN = "token";
|
static final String SETTINGS_KEY_TOKEN = "token";
|
||||||
|
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
|
||||||
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
|
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
|
||||||
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
|
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
|
||||||
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
|
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
|
||||||
@@ -55,9 +60,18 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
String onion = s.get(SETTINGS_KEY_ONION);
|
String onion = s.get(SETTINGS_KEY_ONION);
|
||||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||||
|
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||||
|
// We know we were paired, so we must have proper serverSupports
|
||||||
|
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||||
|
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||||
|
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
||||||
return new MailboxProperties(onion, tokenId, true);
|
return new MailboxProperties(onion, tokenId, serverSupports);
|
||||||
} catch (InvalidMailboxIdException e) {
|
} catch (InvalidMailboxIdException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -69,9 +83,28 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||||
|
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||||
|
int[] ints = new int[serverSupports.size() * 2];
|
||||||
|
int i = 0;
|
||||||
|
for (MailboxVersion v : serverSupports) {
|
||||||
|
ints[i++] = v.getMajor();
|
||||||
|
ints[i++] = v.getMinor();
|
||||||
|
}
|
||||||
|
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
for (MailboxHook hook : hooks) {
|
for (MailboxHook hook : hooks) {
|
||||||
hook.mailboxPaired(txn, p.getOnion());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +143,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
|
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.contact.ContactId;
|
|||||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
@@ -18,11 +19,13 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
||||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
@@ -35,6 +38,7 @@ import org.briarproject.bramble.api.system.Clock;
|
|||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
@@ -44,10 +48,11 @@ import javax.inject.Inject;
|
|||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||||
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
||||||
IncomingMessageHook, MailboxHook {
|
IncomingMessageHook, MailboxHook {
|
||||||
|
|
||||||
|
private final List<MailboxVersion> clientSupports;
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
private final ClientVersioningManager clientVersioningManager;
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
@@ -59,12 +64,14 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
private final Group localGroup;
|
private final Group localGroup;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports,
|
||||||
|
DatabaseComponent db, ClientHelper clientHelper,
|
||||||
ClientVersioningManager clientVersioningManager,
|
ClientVersioningManager clientVersioningManager,
|
||||||
MetadataParser metadataParser,
|
MetadataParser metadataParser,
|
||||||
ContactGroupFactory contactGroupFactory, Clock clock,
|
ContactGroupFactory contactGroupFactory, Clock clock,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
CryptoComponent crypto) {
|
CryptoComponent crypto) {
|
||||||
|
this.clientSupports = clientSupports;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
this.clientVersioningManager = clientVersioningManager;
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
@@ -80,12 +87,46 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
@Override
|
@Override
|
||||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||||
if (db.containsGroup(txn, localGroup.getId())) {
|
if (db.containsGroup(txn, localGroup.getId())) {
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
db.addGroup(txn, localGroup);
|
|
||||||
// Set things up for any pre-existing contacts
|
try {
|
||||||
for (Contact c : db.getContacts(txn)) {
|
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
||||||
addingContact(txn, c);
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
encodeSupportsList(clientSupports)));
|
||||||
|
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +141,15 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
// If we are paired, create and send props to the newly added contact
|
|
||||||
MailboxProperties ownProps =
|
MailboxProperties ownProps =
|
||||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||||
if (ownProps != null) {
|
if (ownProps != null) {
|
||||||
createAndSendProperties(txn, c, ownProps.getOnion());
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,17 +159,17 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mailboxPaired(Transaction txn, String ownOnion)
|
public void mailboxPaired(Transaction txn, String ownOnion,
|
||||||
throws DbException {
|
List<MailboxVersion> serverSupports) throws DbException {
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
createAndSendProperties(txn, c, ownOnion);
|
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
sendEmptyProperties(txn, c);
|
sendUpdateNoMailbox(txn, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +201,8 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
}
|
}
|
||||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||||
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
||||||
MailboxPropertiesUpdate p = parseProperties(body);
|
MailboxUpdate u = parseUpdate(body);
|
||||||
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
|
txn.attach(new RemoteMailboxUpdateEvent(c, u));
|
||||||
// Reset message retransmission timers for the contact. Avoiding
|
// Reset message retransmission timers for the contact. Avoiding
|
||||||
// messages getting stranded:
|
// messages getting stranded:
|
||||||
// - on our mailbox, if they now have a mailbox but didn't before
|
// - on our mailbox, if they now have a mailbox but didn't before
|
||||||
@@ -171,70 +216,82 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||||
public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
|
throws DbException {
|
||||||
ContactId c) throws DbException {
|
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true);
|
||||||
return getProperties(txn, db.getContact(txn, c), true);
|
// An update (with or without mailbox) is created when contact is added
|
||||||
|
if (local == null) {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
|
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||||
ContactId c) throws DbException {
|
throws DbException {
|
||||||
return getProperties(txn, db.getContact(txn, c), false);
|
return getUpdate(txn, db.getContact(txn, c), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and sends an update message to the given contact. The message
|
* Creates and sends an update message to the given contact. The message
|
||||||
* holds our own mailbox's onion, and generated unique properties. All of
|
* holds our own mailbox's onion, generated unique properties, and lists of
|
||||||
* which the contact needs to communicate with our Mailbox.
|
* supported Mailbox API version(s). All of which the contact needs to
|
||||||
|
* communicate with our Mailbox.
|
||||||
*/
|
*/
|
||||||
private void createAndSendProperties(Transaction txn,
|
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
|
||||||
Contact c, String ownOnion) throws DbException {
|
List<MailboxVersion> serverSupports, String ownOnion)
|
||||||
MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
|
throws DbException {
|
||||||
|
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
|
||||||
|
MailboxProperties properties = new MailboxProperties(baseUrl,
|
||||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||||
|
serverSupports,
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||||
|
MailboxUpdate u =
|
||||||
|
new MailboxUpdateWithMailbox(clientSupports, properties);
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
storeMessageReplaceLatest(txn, g.getId(), p);
|
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an empty update message to the given contact. The empty update
|
* Sends an update message with empty properties to the given contact. The
|
||||||
* indicates for the receiving contact that we no longer have a Mailbox that
|
* empty update indicates for the receiving contact that we don't have any
|
||||||
* they can use.
|
* Mailbox that they can use. It still includes the list of Mailbox API
|
||||||
|
* version(s) that we support as a client.
|
||||||
*/
|
*/
|
||||||
private void sendEmptyProperties(Transaction txn, Contact c)
|
private void sendUpdateNoMailbox(Transaction txn, Contact c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
storeMessageReplaceLatest(txn, g.getId(), null);
|
MailboxUpdate u = new MailboxUpdate(clientSupports);
|
||||||
|
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private MailboxPropertiesUpdate getProperties(Transaction txn,
|
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local)
|
||||||
Contact c, boolean local) throws DbException {
|
throws DbException {
|
||||||
MailboxPropertiesUpdate p = null;
|
MailboxUpdate u = null;
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
try {
|
try {
|
||||||
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
||||||
if (latest != null) {
|
if (latest != null) {
|
||||||
BdfList body =
|
BdfList body =
|
||||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
p = parseProperties(body);
|
u = parseUpdate(body);
|
||||||
}
|
}
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
return p;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
||||||
@Nullable MailboxPropertiesUpdate p) throws DbException {
|
MailboxUpdate u) throws DbException {
|
||||||
try {
|
try {
|
||||||
LatestUpdate latest = findLatest(txn, g, true);
|
LatestUpdate latest = findLatest(txn, g, true);
|
||||||
long version = latest == null ? 1 : latest.version + 1;
|
long version = latest == null ? 1 : latest.version + 1;
|
||||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||||
encodeProperties(version, p));
|
encodeProperties(version, u));
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_VERSION, version);
|
meta.put(MSG_KEY_VERSION, version);
|
||||||
meta.put(MSG_KEY_LOCAL, true);
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
@@ -266,23 +323,36 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfList encodeProperties(long version,
|
private BdfList encodeProperties(long version, MailboxUpdate u) {
|
||||||
@Nullable MailboxPropertiesUpdate p) {
|
|
||||||
BdfDictionary dict = new BdfDictionary();
|
BdfDictionary dict = new BdfDictionary();
|
||||||
if (p != null) {
|
BdfList serverSupports = new BdfList();
|
||||||
dict.put(PROP_KEY_ONION, p.getOnion());
|
if (u.hasMailbox()) {
|
||||||
dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
|
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u;
|
||||||
dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
|
MailboxProperties properties = um.getMailboxProperties();
|
||||||
dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
|
serverSupports = encodeSupportsList(properties.getServerSupports());
|
||||||
|
dict.put(PROP_KEY_ONION, properties.getOnion());
|
||||||
|
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
|
||||||
|
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
|
||||||
|
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
|
||||||
}
|
}
|
||||||
return BdfList.of(version, dict);
|
return BdfList.of(version, encodeSupportsList(u.getClientSupports()),
|
||||||
|
serverSupports, dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) {
|
||||||
private MailboxPropertiesUpdate parseProperties(BdfList body)
|
BdfList supports = new BdfList();
|
||||||
throws FormatException {
|
for (MailboxVersion version : supportsList) {
|
||||||
BdfDictionary dict = body.getDictionary(1);
|
supports.add(BdfList.of(version.getMajor(), version.getMinor()));
|
||||||
return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
|
}
|
||||||
|
return supports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MailboxUpdate parseUpdate(BdfList body) throws FormatException {
|
||||||
|
BdfList clientSupports = body.getList(1);
|
||||||
|
BdfList serverSupports = body.getList(2);
|
||||||
|
BdfDictionary dict = body.getDictionary(3);
|
||||||
|
return clientHelper.parseAndValidateMailboxUpdate(clientSupports,
|
||||||
|
serverSupports, dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
private Group getContactGroup(Contact c) {
|
||||||
@@ -15,15 +15,15 @@ import org.briarproject.bramble.api.system.Clock;
|
|||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxPropertyValidator extends BdfMessageValidator {
|
class MailboxUpdateValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
MailboxPropertyValidator(ClientHelper clientHelper,
|
MailboxUpdateValidator(ClientHelper clientHelper,
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
super(clientHelper, metadataEncoder, clock);
|
super(clientHelper, metadataEncoder, clock);
|
||||||
}
|
}
|
||||||
@@ -31,14 +31,19 @@ class MailboxPropertyValidator extends BdfMessageValidator {
|
|||||||
@Override
|
@Override
|
||||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||||
BdfList body) throws InvalidMessageException, FormatException {
|
BdfList body) throws InvalidMessageException, FormatException {
|
||||||
// Version, properties
|
// Version, Properties, clientSupports, serverSupports
|
||||||
checkSize(body, 2);
|
checkSize(body, 4);
|
||||||
// Version
|
// Version
|
||||||
long version = body.getLong(0);
|
long version = body.getLong(0);
|
||||||
if (version < 0) throw new FormatException();
|
if (version < 0) throw new FormatException();
|
||||||
|
// clientSupports
|
||||||
|
BdfList clientSupports = body.getList(1);
|
||||||
|
// serverSupports
|
||||||
|
BdfList serverSupports = body.getList(2);
|
||||||
// Properties
|
// Properties
|
||||||
BdfDictionary dictionary = body.getDictionary(1);
|
BdfDictionary dictionary = body.getDictionary(3);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary);
|
clientHelper.parseAndValidateMailboxUpdate(clientSupports,
|
||||||
|
serverSupports, dictionary);
|
||||||
// Return the metadata
|
// Return the metadata
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_VERSION, version);
|
meta.put(MSG_KEY_VERSION, version);
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience class for making simple API calls that don't return values.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public abstract class SimpleApiCall implements ApiCall {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
||||||
|
|
||||||
|
abstract void tryToCallApi()
|
||||||
|
throws IOException, ApiException, TolerableFailureException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean callApi() {
|
||||||
|
try {
|
||||||
|
tryToCallApi();
|
||||||
|
return false; // Succeeded, don't retry
|
||||||
|
} catch (IOException | ApiException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return true; // Failed, retry with backoff
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
logException(LOG, INFO, e);
|
||||||
|
return false; // Failed tolerably, don't retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin;
|
package org.briarproject.bramble.plugin;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
@@ -27,7 +28,6 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
|||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.system.Wakeful;
|
import org.briarproject.bramble.api.system.Wakeful;
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
|
|
||||||
import net.freehaven.tor.control.EventHandler;
|
import net.freehaven.tor.control.EventHandler;
|
||||||
import net.freehaven.tor.control.TorControlConnection;
|
import net.freehaven.tor.control.TorControlConnection;
|
||||||
|
import net.freehaven.tor.control.TorNotRunningException;
|
||||||
|
|
||||||
import org.briarproject.bramble.PoliteExecutor;
|
import org.briarproject.bramble.PoliteExecutor;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
@@ -321,6 +322,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
LOG.info("Tor has already built a circuit");
|
LOG.info("Tor has already built a circuit");
|
||||||
state.getAndSetCircuitBuilt(true);
|
state.getAndSetCircuitBuilt(true);
|
||||||
}
|
}
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
@@ -492,6 +495,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
} else {
|
} else {
|
||||||
response = controlConnection.addOnion(privKey, portLines);
|
response = controlConnection.addOnion(privKey, portLines);
|
||||||
}
|
}
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return;
|
return;
|
||||||
@@ -540,30 +545,38 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
protected void enableNetwork(boolean enable) throws IOException {
|
protected void enableNetwork(boolean enable) throws IOException {
|
||||||
state.enableNetwork(enable);
|
state.enableNetwork(enable);
|
||||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
try {
|
||||||
|
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (enable) {
|
try {
|
||||||
Collection<String> conf = new ArrayList<>();
|
if (enable) {
|
||||||
conf.add("UseBridges 1");
|
Collection<String> conf = new ArrayList<>();
|
||||||
File obfs4File = getObfs4ExecutableFile();
|
conf.add("UseBridges 1");
|
||||||
if (bridgeTypes.contains(MEEK)) {
|
File obfs4File = getObfs4ExecutableFile();
|
||||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
if (bridgeTypes.contains(MEEK)) {
|
||||||
obfs4File.getAbsolutePath());
|
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 (bridgeTypes.contains(DEFAULT_OBFS4) ||
|
} catch (TorNotRunningException e) {
|
||||||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
|
throw new RuntimeException(e);
|
||||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
|
||||||
obfs4File.getAbsolutePath());
|
|
||||||
}
|
|
||||||
for (BridgeType bridgeType : bridgeTypes) {
|
|
||||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
|
||||||
}
|
|
||||||
controlConnection.setConf(conf);
|
|
||||||
} else {
|
|
||||||
controlConnection.setConf("UseBridges", "0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,6 +590,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
controlConnection.setConf("DisableNetwork", "1");
|
controlConnection.setConf("DisableNetwork", "1");
|
||||||
controlConnection.shutdownTor("TERM");
|
controlConnection.shutdownTor("TERM");
|
||||||
controlSocket.close();
|
controlSocket.close();
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -708,7 +723,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
});
|
});
|
||||||
Map<Integer, String> portLines =
|
Map<Integer, String> portLines =
|
||||||
singletonMap(80, "127.0.0.1:" + port);
|
singletonMap(80, "127.0.0.1:" + port);
|
||||||
controlConnection.addOnion(blob, portLines);
|
try {
|
||||||
|
controlConnection.addOnion(blob, portLines);
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
return new RendezvousEndpoint() {
|
return new RendezvousEndpoint() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -718,7 +737,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
controlConnection.delOnion(localOnion);
|
try {
|
||||||
|
controlConnection.delOnion(localOnion);
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
tryToClose(ss, LOG, WARNING);
|
tryToClose(ss, LOG, WARNING);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -746,18 +769,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
public void orConnStatus(String status, String orName) {
|
public void orConnStatus(String status, String orName) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
|
|
||||||
//noinspection IfCanBeSwitch
|
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
||||||
if (status.equals("LAUNCHED")) state.onOrConnectionLaunched();
|
|
||||||
else if (status.equals("FAILED")) state.onOrConnectionFailed();
|
|
||||||
else if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
|
||||||
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
||||||
|
|
||||||
if ((status.equals("FAILED") || status.equals("CLOSED")) &&
|
|
||||||
state.getNumOrConnections() == 0) {
|
|
||||||
// Check whether we've lost connectivity
|
|
||||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
|
||||||
batteryManager.isCharging());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -771,9 +784,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void message(String severity, String msg) {
|
public void message(String severity, String msg) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
||||||
if (msg.startsWith("Switching to guard context")) {
|
|
||||||
state.onSwitchingGuardContext();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -855,11 +865,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (s.getNamespace().equals(ID.getString())) {
|
if (s.getNamespace().equals(ID.getString())) {
|
||||||
LOG.info("Tor settings updated");
|
LOG.info("Tor settings updated");
|
||||||
settings = s.getSettings();
|
settings = s.getSettings();
|
||||||
// Works around a bug introduced in Tor 0.3.4.8.
|
|
||||||
// https://trac.torproject.org/projects/tor/ticket/28027
|
|
||||||
// Could be replaced with callback.transportDisabled()
|
|
||||||
// when fixed.
|
|
||||||
disableNetwork();
|
|
||||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||||
batteryManager.isCharging());
|
batteryManager.isCharging());
|
||||||
}
|
}
|
||||||
@@ -872,16 +877,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disableNetwork() {
|
|
||||||
connectionStatusExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
if (state.isTorRunning()) enableNetwork(false);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logException(LOG, WARNING, ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateConnectionStatus(NetworkStatus status,
|
private void updateConnectionStatus(NetworkStatus status,
|
||||||
boolean charging) {
|
boolean charging) {
|
||||||
connectionStatusExecutor.execute(() -> {
|
connectionStatusExecutor.execute(() -> {
|
||||||
@@ -978,12 +973,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enableConnectionPadding(boolean enable) throws IOException {
|
private void enableConnectionPadding(boolean enable) throws IOException {
|
||||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
try {
|
||||||
|
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useIpv6(boolean ipv6Only) throws IOException {
|
private void useIpv6(boolean ipv6Only) throws IOException {
|
||||||
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
|
try {
|
||||||
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
|
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
|
||||||
|
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
|
||||||
|
} catch (TorNotRunningException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@@ -1007,13 +1010,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private ServerSocket serverSocket = null;
|
private ServerSocket serverSocket = null;
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private int orConnectionsPending = 0, orConnectionsConnected = 0;
|
private int orConnectionsConnected = 0;
|
||||||
|
|
||||||
private synchronized void setStarted() {
|
private synchronized void setStarted() {
|
||||||
started = true;
|
started = true;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
private synchronized boolean isTorRunning() {
|
private synchronized boolean isTorRunning() {
|
||||||
return started && !stopped;
|
return started && !stopped;
|
||||||
}
|
}
|
||||||
@@ -1071,63 +1075,38 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (reasonsDisabled != 0) return DISABLED;
|
if (reasonsDisabled != 0) return DISABLED;
|
||||||
if (!networkInitialised) return ENABLING;
|
if (!networkInitialised) return ENABLING;
|
||||||
if (!networkEnabled) return INACTIVE;
|
if (!networkEnabled) return INACTIVE;
|
||||||
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
|
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
|
||||||
|
? ACTIVE : ENABLING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized int getReasonsDisabled() {
|
private synchronized int getReasonsDisabled() {
|
||||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onOrConnectionLaunched() {
|
|
||||||
orConnectionsPending++;
|
|
||||||
logOrConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void onOrConnectionFailed() {
|
|
||||||
orConnectionsPending--;
|
|
||||||
if (orConnectionsPending < 0) {
|
|
||||||
LOG.warning("Count was zero before connection failed");
|
|
||||||
orConnectionsPending = 0;
|
|
||||||
}
|
|
||||||
logOrConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void onOrConnectionConnected() {
|
private synchronized void onOrConnectionConnected() {
|
||||||
orConnectionsPending--;
|
int oldConnected = orConnectionsConnected;
|
||||||
if (orConnectionsPending < 0) {
|
|
||||||
LOG.warning("Count was zero before connection connected");
|
|
||||||
orConnectionsPending = 0;
|
|
||||||
}
|
|
||||||
orConnectionsConnected++;
|
orConnectionsConnected++;
|
||||||
logOrConnections();
|
logOrConnections();
|
||||||
|
if (oldConnected == 0) callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onOrConnectionClosed() {
|
private synchronized void onOrConnectionClosed() {
|
||||||
|
int oldConnected = orConnectionsConnected;
|
||||||
orConnectionsConnected--;
|
orConnectionsConnected--;
|
||||||
if (orConnectionsConnected < 0) {
|
if (orConnectionsConnected < 0) {
|
||||||
LOG.warning("Count was zero before connection closed");
|
LOG.warning("Count was zero before connection closed");
|
||||||
orConnectionsConnected = 0;
|
orConnectionsConnected = 0;
|
||||||
}
|
}
|
||||||
logOrConnections();
|
logOrConnections();
|
||||||
}
|
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
||||||
|
callback.pluginStateChanged(getState());
|
||||||
private synchronized void onSwitchingGuardContext() {
|
}
|
||||||
// Tor doesn't seem to report events for connections belonging to
|
|
||||||
// the old guard context, so we have to reset the counters
|
|
||||||
orConnectionsPending = 0;
|
|
||||||
orConnectionsConnected = 0;
|
|
||||||
logOrConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized int getNumOrConnections() {
|
|
||||||
return orConnectionsPending + orConnectionsConnected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private void logOrConnections() {
|
private void logOrConnections() {
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("OR connections: " + orConnectionsPending
|
LOG.info(orConnectionsConnected + " OR connections connected");
|
||||||
+ " pending, " + orConnectionsConnected + " connected");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||||
|
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||||
|
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||||
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
|
protected static final Logger LOG =
|
||||||
|
getLogger(TorPluginFactory.class.getName());
|
||||||
|
|
||||||
|
protected static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
|
protected static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
|
||||||
|
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||||
|
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
||||||
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
|
protected final Executor ioExecutor, wakefulIoExecutor;
|
||||||
|
protected final NetworkManager networkManager;
|
||||||
|
protected final LocationUtils locationUtils;
|
||||||
|
protected final EventBus eventBus;
|
||||||
|
protected final SocketFactory torSocketFactory;
|
||||||
|
protected final BackoffFactory backoffFactory;
|
||||||
|
protected final ResourceProvider resourceProvider;
|
||||||
|
protected final CircumventionProvider circumventionProvider;
|
||||||
|
protected final BatteryManager batteryManager;
|
||||||
|
protected final Clock clock;
|
||||||
|
protected final CryptoComponent crypto;
|
||||||
|
protected final File torDirectory;
|
||||||
|
protected final int torSocksPort;
|
||||||
|
protected final int torControlPort;
|
||||||
|
|
||||||
|
TorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
|
NetworkManager networkManager,
|
||||||
|
LocationUtils locationUtils,
|
||||||
|
EventBus eventBus,
|
||||||
|
SocketFactory torSocketFactory,
|
||||||
|
BackoffFactory backoffFactory,
|
||||||
|
ResourceProvider resourceProvider,
|
||||||
|
CircumventionProvider circumventionProvider,
|
||||||
|
BatteryManager batteryManager,
|
||||||
|
Clock clock,
|
||||||
|
CryptoComponent crypto,
|
||||||
|
@TorDirectory File torDirectory,
|
||||||
|
@TorSocksPort int torSocksPort,
|
||||||
|
@TorControlPort int torControlPort) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||||
|
this.networkManager = networkManager;
|
||||||
|
this.locationUtils = locationUtils;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.torSocketFactory = torSocketFactory;
|
||||||
|
this.backoffFactory = backoffFactory;
|
||||||
|
this.resourceProvider = resourceProvider;
|
||||||
|
this.circumventionProvider = circumventionProvider;
|
||||||
|
this.batteryManager = batteryManager;
|
||||||
|
this.clock = clock;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.torDirectory = torDirectory;
|
||||||
|
this.torSocksPort = torSocksPort;
|
||||||
|
this.torControlPort = torControlPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract String getArchitectureForTorBinary();
|
||||||
|
|
||||||
|
abstract TorPlugin createPluginInstance(Backoff backoff,
|
||||||
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
|
String architecture);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportId getId() {
|
||||||
|
return TorConstants.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxLatency() {
|
||||||
|
return MAX_LATENCY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||||
|
// Check that we have a Tor binary for this architecture
|
||||||
|
String architecture = getArchitectureForTorBinary();
|
||||||
|
if (architecture == null) {
|
||||||
|
LOG.warning("Tor is not supported on this architecture");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("The selected architecture for Tor is " + architecture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
|
TorRendezvousCrypto torRendezvousCrypto =
|
||||||
|
new TorRendezvousCryptoImpl(crypto);
|
||||||
|
TorPlugin plugin = createPluginInstance(backoff, torRendezvousCrypto,
|
||||||
|
callback, architecture);
|
||||||
|
eventBus.addListener(plugin);
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ class RecordWriterImpl implements RecordWriter {
|
|||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
||||||
|
|
||||||
|
private long bytesWritten = 0;
|
||||||
|
|
||||||
RecordWriterImpl(OutputStream out) {
|
RecordWriterImpl(OutputStream out) {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
@@ -31,6 +33,7 @@ class RecordWriterImpl implements RecordWriter {
|
|||||||
ByteUtils.writeUint16(payload.length, header, 2);
|
ByteUtils.writeUint16(payload.length, header, 2);
|
||||||
out.write(header);
|
out.write(header);
|
||||||
out.write(payload);
|
out.write(payload);
|
||||||
|
bytesWritten += RECORD_HEADER_BYTES + payload.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,4 +45,9 @@ class RecordWriterImpl implements RecordWriter {
|
|||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBytesWritten() {
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.rendezvous;
|
package org.briarproject.bramble.rendezvous;
|
||||||
|
|
||||||
import org.briarproject.bramble.PoliteExecutor;
|
import org.briarproject.bramble.PoliteExecutor;
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.contact.PendingContact;
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
@@ -42,7 +43,6 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
|
|||||||
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
import org.briarproject.bramble.api.system.Wakeful;
|
import org.briarproject.bramble.api.system.Wakeful;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_CONNECT_TIMEOUT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -20,6 +21,6 @@ public class SocksModule {
|
|||||||
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
||||||
torSocksPort);
|
torSocksPort);
|
||||||
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
|
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
|
||||||
EXTRA_SOCKET_TIMEOUT);
|
EXTRA_CONNECT_TIMEOUT, EXTRA_SOCKET_TIMEOUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,15 +26,18 @@ class SocksSocket extends Socket {
|
|||||||
"Address type not supported"
|
"Address type not supported"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("MismatchedReadAndWriteOfArray")
|
||||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout, extraSocketTimeout;
|
private final int connectToProxyTimeout;
|
||||||
|
private final int extraConnectTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
|
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
int extraSocketTimeout) {
|
int extraConnectTimeout, int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraConnectTimeout = extraConnectTimeout;
|
||||||
this.extraSocketTimeout = extraSocketTimeout;
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ class SocksSocket extends Socket {
|
|||||||
|
|
||||||
// Use the supplied timeout temporarily, plus any configured extra
|
// Use the supplied timeout temporarily, plus any configured extra
|
||||||
int oldTimeout = getSoTimeout();
|
int oldTimeout = getSoTimeout();
|
||||||
setSoTimeout(timeout + extraSocketTimeout);
|
setSoTimeout(timeout + extraConnectTimeout);
|
||||||
|
|
||||||
// Connect to the endpoint via the proxy
|
// Connect to the endpoint via the proxy
|
||||||
sendConnectRequest(out, host, port);
|
sendConnectRequest(out, host, port);
|
||||||
|
|||||||
@@ -11,18 +11,21 @@ import javax.net.SocketFactory;
|
|||||||
class SocksSocketFactory extends SocketFactory {
|
class SocksSocketFactory extends SocketFactory {
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout, extraSocketTimeout;
|
private final int connectToProxyTimeout;
|
||||||
|
private final int extraConnectTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
|
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
int extraSocketTimeout) {
|
int extraConnectTimeout, int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraConnectTimeout = extraConnectTimeout;
|
||||||
this.extraSocketTimeout = extraSocketTimeout;
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createSocket() {
|
public Socket createSocket() {
|
||||||
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
|
return new SocksSocket(proxy, connectToProxyTimeout,
|
||||||
|
extraConnectTimeout, extraSocketTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.Priority;
|
import org.briarproject.bramble.api.sync.Priority;
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncConstants;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.Versions;
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
@@ -47,8 +49,9 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -71,6 +74,16 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
NEXT_SEND_TIME_DECREASED = () -> {
|
NEXT_SEND_TIME_DECREASED = () -> {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The batch capacity must be at least {@link Record#RECORD_HEADER_BYTES}
|
||||||
|
* + {@link SyncConstants#MAX_MESSAGE_LENGTH} to ensure that maximum-size
|
||||||
|
* messages can be selected for transmission. Larger batches will mean
|
||||||
|
* fewer round-trips between the DB and the output stream, but each
|
||||||
|
* round-trip will block the DB for longer.
|
||||||
|
*/
|
||||||
|
private static final int BATCH_CAPACITY =
|
||||||
|
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
@@ -296,8 +309,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
db.transactionWithNullableResult(false, txn -> {
|
db.transactionWithNullableResult(false, txn -> {
|
||||||
Collection<Message> batch =
|
Collection<Message> batch =
|
||||||
db.generateRequestedBatch(txn, contactId,
|
db.generateRequestedBatch(txn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_BYTES,
|
BATCH_CAPACITY, maxLatency);
|
||||||
maxLatency);
|
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||||
return batch;
|
return batch;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link SimplexOutgoingSession} that sends messages eagerly, ie
|
||||||
|
* regardless of whether they're due for retransmission.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class EagerSimplexOutgoingSession extends SimplexOutgoingSession {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(EagerSimplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
|
EagerSimplexOutgoingSession(DatabaseComponent db,
|
||||||
|
EventBus eventBus,
|
||||||
|
ContactId contactId,
|
||||||
|
TransportId transportId,
|
||||||
|
long maxLatency,
|
||||||
|
StreamWriter streamWriter,
|
||||||
|
SyncRecordWriter recordWriter) {
|
||||||
|
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
||||||
|
recordWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendMessages() throws DbException, IOException {
|
||||||
|
for (MessageId m : loadUnackedMessageIdsToSend()) {
|
||||||
|
if (isInterrupted()) break;
|
||||||
|
Message message = db.transactionWithNullableResult(false, txn ->
|
||||||
|
db.getMessageToSend(txn, contactId, m, maxLatency, true));
|
||||||
|
if (message == null) continue; // No longer shared
|
||||||
|
recordWriter.writeMessage(message);
|
||||||
|
LOG.info("Sent message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<MessageId> loadUnackedMessageIdsToSend()
|
||||||
|
throws DbException {
|
||||||
|
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
||||||
|
db.getUnackedMessagesToSend(txn, contactId));
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(ids.size() + " unacked messages to send");
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
|
import org.briarproject.bramble.api.sync.DeferredSendHandler;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link SimplexOutgoingSession} for sending and acking messages via a
|
||||||
|
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
|
||||||
|
* of the messages sent and acked during the session so that they can be
|
||||||
|
* recorded in the DB as sent or acked after the file has been successfully
|
||||||
|
* uploaded to the mailbox.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(MailboxOutgoingSession.class.getName());
|
||||||
|
|
||||||
|
private final DeferredSendHandler deferredSendHandler;
|
||||||
|
private final long initialCapacity;
|
||||||
|
|
||||||
|
MailboxOutgoingSession(DatabaseComponent db,
|
||||||
|
EventBus eventBus,
|
||||||
|
ContactId contactId,
|
||||||
|
TransportId transportId,
|
||||||
|
long maxLatency,
|
||||||
|
StreamWriter streamWriter,
|
||||||
|
SyncRecordWriter recordWriter,
|
||||||
|
DeferredSendHandler deferredSendHandler,
|
||||||
|
long capacity) {
|
||||||
|
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
||||||
|
recordWriter);
|
||||||
|
this.deferredSendHandler = deferredSendHandler;
|
||||||
|
this.initialCapacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendAcks() throws DbException, IOException {
|
||||||
|
while (!isInterrupted()) {
|
||||||
|
Collection<MessageId> idsToAck = loadMessageIdsToAck();
|
||||||
|
if (idsToAck.isEmpty()) break;
|
||||||
|
recordWriter.writeAck(new Ack(idsToAck));
|
||||||
|
deferredSendHandler.onAckSent(idsToAck);
|
||||||
|
LOG.info("Sent ack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<MessageId> loadMessageIdsToAck() throws DbException {
|
||||||
|
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
|
||||||
|
/ MessageId.LENGTH;
|
||||||
|
if (idCapacity <= 0) return emptyList(); // Out of capacity
|
||||||
|
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
||||||
|
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
||||||
|
db.getMessagesToAck(txn, contactId, maxMessageIds));
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(ids.size() + " messages to ack");
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getRemainingCapacity() {
|
||||||
|
return initialCapacity - recordWriter.getBytesWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendMessages() throws DbException, IOException {
|
||||||
|
for (MessageId m : loadMessageIdsToSend()) {
|
||||||
|
if (isInterrupted()) break;
|
||||||
|
// Defer marking the message as sent
|
||||||
|
Message message = db.transactionWithNullableResult(true, txn ->
|
||||||
|
db.getMessageToSend(txn, contactId, m, maxLatency, false));
|
||||||
|
if (message == null) continue; // No longer shared
|
||||||
|
recordWriter.writeMessage(message);
|
||||||
|
deferredSendHandler.onMessageSent(m);
|
||||||
|
LOG.info("Sent message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<MessageId> loadMessageIdsToSend() throws DbException {
|
||||||
|
long capacity = getRemainingCapacity();
|
||||||
|
if (capacity < RECORD_HEADER_BYTES + MESSAGE_HEADER_LENGTH) {
|
||||||
|
return emptyList(); // Out of capacity
|
||||||
|
}
|
||||||
|
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
||||||
|
db.getMessagesToSend(txn, contactId, capacity, maxLatency));
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(ids.size() + " messages to send");
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.sync;
|
|||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
@@ -13,9 +12,10 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.SyncConstants;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.Versions;
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
@@ -23,15 +23,7 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -40,8 +32,9 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -57,38 +50,40 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(SimplexOutgoingSession.class.getName());
|
getLogger(SimplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
/**
|
||||||
};
|
* The batch capacity must be at least {@link Record#RECORD_HEADER_BYTES}
|
||||||
|
* + {@link SyncConstants#MAX_MESSAGE_LENGTH} to ensure that maximum-size
|
||||||
|
* messages can be selected for transmission. Larger batches will mean
|
||||||
|
* fewer round-trips between the DB and the output stream, but each
|
||||||
|
* round-trip will block the DB for longer.
|
||||||
|
*/
|
||||||
|
static final int BATCH_CAPACITY =
|
||||||
|
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
protected final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
protected final EventBus eventBus;
|
||||||
private final EventBus eventBus;
|
protected final ContactId contactId;
|
||||||
private final ContactId contactId;
|
protected final TransportId transportId;
|
||||||
private final TransportId transportId;
|
protected final long maxLatency;
|
||||||
private final long maxLatency;
|
protected final StreamWriter streamWriter;
|
||||||
private final boolean eager;
|
protected final SyncRecordWriter recordWriter;
|
||||||
private final StreamWriter streamWriter;
|
|
||||||
private final SyncRecordWriter recordWriter;
|
|
||||||
private final AtomicInteger outstandingQueries;
|
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
|
||||||
|
|
||||||
private volatile boolean interrupted = false;
|
private volatile boolean interrupted = false;
|
||||||
|
|
||||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
SimplexOutgoingSession(DatabaseComponent db,
|
||||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
EventBus eventBus,
|
||||||
long maxLatency, boolean eager, StreamWriter streamWriter,
|
ContactId contactId,
|
||||||
|
TransportId transportId,
|
||||||
|
long maxLatency,
|
||||||
|
StreamWriter streamWriter,
|
||||||
SyncRecordWriter recordWriter) {
|
SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
this.eager = eager;
|
|
||||||
this.streamWriter = streamWriter;
|
this.streamWriter = streamWriter;
|
||||||
this.recordWriter = recordWriter;
|
this.recordWriter = recordWriter;
|
||||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
|
||||||
writerTasks = new LinkedBlockingQueue<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
@@ -98,22 +93,13 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
try {
|
try {
|
||||||
// Send our supported protocol versions
|
// Send our supported protocol versions
|
||||||
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
|
||||||
dbExecutor.execute(this::generateAck);
|
|
||||||
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
|
|
||||||
else dbExecutor.execute(this::generateBatch);
|
|
||||||
// Write records until interrupted or no more records to write
|
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
sendAcks();
|
||||||
ThrowingRunnable<IOException> task = writerTasks.take();
|
sendMessages();
|
||||||
if (task == CLOSE) break;
|
} catch (DbException e) {
|
||||||
task.run();
|
logException(LOG, WARNING, e);
|
||||||
}
|
|
||||||
streamWriter.sendEndOfStream();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Interrupted while waiting for a record to write");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
}
|
||||||
|
streamWriter.sendEndOfStream();
|
||||||
} finally {
|
} finally {
|
||||||
eventBus.removeListener(this);
|
eventBus.removeListener(this);
|
||||||
}
|
}
|
||||||
@@ -122,11 +108,10 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
writerTasks.add(CLOSE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decrementOutstandingQueries() {
|
boolean isInterrupted() {
|
||||||
if (outstandingQueries.decrementAndGet() == 0) writerTasks.add(CLOSE);
|
return interrupted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -146,110 +131,33 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
void sendAcks() throws DbException, IOException {
|
||||||
private void loadUnackedMessageIds() {
|
while (!isInterrupted()) if (!generateAndSendAck()) break;
|
||||||
if (interrupted) return;
|
|
||||||
try {
|
|
||||||
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
|
|
||||||
db.getUnackedMessagesToSend(txn, contactId));
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info(ids.size() + " unacked messages to send");
|
|
||||||
}
|
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
private boolean generateAndSendAck() throws DbException, IOException {
|
||||||
private void generateEagerBatch(Map<MessageId, Integer> ids) {
|
Ack a = db.transactionWithNullableResult(false, txn ->
|
||||||
if (interrupted) return;
|
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
||||||
// Take some message IDs from `ids` to form a batch
|
if (LOG.isLoggable(INFO))
|
||||||
Collection<MessageId> batchIds = new ArrayList<>();
|
LOG.info("Generated ack: " + (a != null));
|
||||||
long totalLength = 0;
|
if (a == null) return false; // No more acks to send
|
||||||
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
|
recordWriter.writeAck(a);
|
||||||
while (it.hasNext()) {
|
|
||||||
// Check whether the next message will fit in the batch
|
|
||||||
Entry<MessageId, Integer> e = it.next();
|
|
||||||
int length = e.getValue();
|
|
||||||
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
|
|
||||||
// Add the message to the batch
|
|
||||||
it.remove();
|
|
||||||
batchIds.add(e.getKey());
|
|
||||||
totalLength += length;
|
|
||||||
}
|
|
||||||
if (batchIds.isEmpty()) throw new AssertionError();
|
|
||||||
try {
|
|
||||||
Collection<Message> batch =
|
|
||||||
db.transactionWithResult(false, txn ->
|
|
||||||
db.generateBatch(txn, contactId, batchIds,
|
|
||||||
maxLatency));
|
|
||||||
writerTasks.add(() -> writeEagerBatch(batch, ids));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void writeEagerBatch(Collection<Message> batch,
|
|
||||||
Map<MessageId, Integer> ids) throws IOException {
|
|
||||||
if (interrupted) return;
|
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
|
||||||
LOG.info("Sent eager batch");
|
|
||||||
if (ids.isEmpty()) decrementOutstandingQueries();
|
|
||||||
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
private void generateAck() {
|
|
||||||
if (interrupted) return;
|
|
||||||
try {
|
|
||||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
|
||||||
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Generated ack: " + (a != null));
|
|
||||||
if (a == null) decrementOutstandingQueries();
|
|
||||||
else writerTasks.add(() -> writeAck(a));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void writeAck(Ack ack) throws IOException {
|
|
||||||
if (interrupted) return;
|
|
||||||
recordWriter.writeAck(ack);
|
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
dbExecutor.execute(this::generateAck);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
void sendMessages() throws DbException, IOException {
|
||||||
private void generateBatch() {
|
while (!isInterrupted()) if (!generateAndSendBatch()) break;
|
||||||
if (interrupted) return;
|
|
||||||
try {
|
|
||||||
Collection<Message> b =
|
|
||||||
db.transactionWithNullableResult(false, txn ->
|
|
||||||
db.generateBatch(txn, contactId,
|
|
||||||
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Generated batch: " + (b != null));
|
|
||||||
if (b == null) decrementOutstandingQueries();
|
|
||||||
else writerTasks.add(() -> writeBatch(b));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
private boolean generateAndSendBatch() throws DbException, IOException {
|
||||||
private void writeBatch(Collection<Message> batch) throws IOException {
|
Collection<Message> b = db.transactionWithNullableResult(false, txn ->
|
||||||
if (interrupted) return;
|
db.generateBatch(txn, contactId, BATCH_CAPACITY, maxLatency));
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Generated batch: " + (b != null));
|
||||||
|
if (b == null) return false; // No more messages to send
|
||||||
|
for (Message m : b) recordWriter.writeMessage(m);
|
||||||
LOG.info("Sent batch");
|
LOG.info("Sent batch");
|
||||||
dbExecutor.execute(this::generateBatch);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,4 +85,9 @@ class SyncRecordWriterImpl implements SyncRecordWriter {
|
|||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
writer.flush();
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBytesWritten() {
|
||||||
|
return writer.getBytesWritten();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,13 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
OutputStream out = streamWriter.getOutputStream();
|
OutputStream out = streamWriter.getOutputStream();
|
||||||
SyncRecordWriter recordWriter =
|
SyncRecordWriter recordWriter =
|
||||||
recordWriterFactory.createRecordWriter(out);
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
if (eager) {
|
||||||
maxLatency, eager, streamWriter, recordWriter);
|
return new EagerSimplexOutgoingSession(db, eventBus, c, t,
|
||||||
|
maxLatency, streamWriter, recordWriter);
|
||||||
|
} else {
|
||||||
|
return new SimplexOutgoingSession(db, eventBus, c, t,
|
||||||
|
maxLatency, streamWriter, recordWriter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
if (v == null) {
|
if (v == null) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
|
if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
|
||||||
} else {
|
} else {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Validating message for " + cv.getClientId());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
MessageContext context = v.validateMessage(m, g);
|
MessageContext context = v.validateMessage(m, g);
|
||||||
storeMessageContextAsync(m, g.getClientId(),
|
storeMessageContextAsync(m, g.getClientId(),
|
||||||
@@ -323,6 +326,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||||
IncomingMessageHook hook = hooks.get(cv);
|
IncomingMessageHook hook = hooks.get(cv);
|
||||||
if (hook == null) return ACCEPT_DO_NOT_SHARE;
|
if (hook == null) return ACCEPT_DO_NOT_SHARE;
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Delivering message for " + c);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return hook.incomingMessage(txn, m, meta);
|
return hook.incomingMessage(txn, m, meta);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.system;
|
package org.briarproject.bramble.system;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,23 @@ d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cer
|
|||||||
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||||
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||||
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||||
|
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||||
|
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||||
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
||||||
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
|
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
|
||||||
n Bridge obfs4 172.105.22.69:80 CBD17B33192A879433AB37C9E142541BD3459ABD cert=rk5YmpKypLsjlS4tjkYaZNBweYMa5tWQRhZ8Q2WRleNOgrhSceKo59BA8kp6kVfaMPXnSw iat-mode=0
|
|
||||||
n Bridge obfs4 46.128.93.192:7346 5D28B8E1D117B8720D56A8513CF32509DCA1D84F cert=ED6tZP50eF0vno09F5gFvoWTMdcWFEX2FtwXOUYRevjzKg30/y701f61Vycnh6HO9gkaMw iat-mode=0
|
|
||||||
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
||||||
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
|
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
|
||||||
n Bridge obfs4 192.3.163.88:57145 DEB62DE9643E5956CA4FA78035B48C9BBABE7F29 cert=RMz2z9uVVrioUhx0A8xUmiftRe2RpcXiqIuDfisdIomcHDf82nzfn83X/ixGUiA4rLCAdw iat-mode=0
|
|
||||||
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
|
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
|
||||||
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
|
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
|
||||||
v Bridge 5.45.96.40:9001 8723B591712AAA03FB92000370BD356AB4997FA7
|
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
|
||||||
|
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
|
||||||
|
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
|
||||||
|
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
|
||||||
|
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
|
||||||
|
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
|
||||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
||||||
v Bridge 152.44.197.85:10507 FF07DF6B4720DA4C50F1A025662D50916D6223F6
|
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
|
||||||
v Bridge 209.216.78.21:443 C870D381E7264CDB83BAEEBF074804808CCCDB8D
|
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
|
||||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||||
@@ -21,9 +21,10 @@ import org.briarproject.bramble.api.db.Metadata;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
@@ -41,24 +42,26 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
|
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
import static org.briarproject.bramble.test.TestUtils.mailboxUpdateEqual;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
@@ -95,25 +98,31 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
|||||||
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
||||||
metadataEncoder, cryptoComponent, authorFactory);
|
metadataEncoder, cryptoComponent, authorFactory);
|
||||||
|
|
||||||
private final MailboxPropertiesUpdate validMailboxPropsUpdate;
|
private final MailboxUpdateWithMailbox validMailboxUpdateWithMailbox;
|
||||||
|
private final BdfList emptyClientSupports;
|
||||||
|
private final BdfList someClientSupports;
|
||||||
|
private final BdfList emptyServerSupports;
|
||||||
|
private final BdfList someServerSupports;
|
||||||
|
|
||||||
public ClientHelperImplTest() {
|
public ClientHelperImplTest() {
|
||||||
validMailboxPropsUpdate = new MailboxPropertiesUpdate(
|
emptyClientSupports = new BdfList();
|
||||||
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd",
|
someClientSupports = BdfList.of(BdfList.of(1, 0));
|
||||||
new MailboxAuthToken(getRandomId()),
|
emptyServerSupports = new BdfList();
|
||||||
new MailboxFolderId(getRandomId()),
|
someServerSupports = BdfList.of(BdfList.of(1, 0));
|
||||||
new MailboxFolderId(getRandomId()));
|
validMailboxUpdateWithMailbox = new MailboxUpdateWithMailbox(
|
||||||
|
singletonList(new MailboxVersion(1, 0)),
|
||||||
|
getMailboxProperties(false,
|
||||||
|
singletonList(new MailboxVersion(1, 0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfDictionary getValidMailboxPropsUpdateDict() {
|
private BdfDictionary getValidMailboxUpdateWithMailboxDict() {
|
||||||
BdfDictionary dict = new BdfDictionary();
|
BdfDictionary dict = new BdfDictionary();
|
||||||
dict.put(PROP_KEY_ONION, validMailboxPropsUpdate.getOnion());
|
MailboxProperties properties =
|
||||||
dict.put(PROP_KEY_AUTHTOKEN, validMailboxPropsUpdate.getAuthToken()
|
validMailboxUpdateWithMailbox.getMailboxProperties();
|
||||||
.getBytes());
|
dict.put(PROP_KEY_ONION, properties.getOnion());
|
||||||
dict.put(PROP_KEY_INBOXID, validMailboxPropsUpdate.getInboxId()
|
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
|
||||||
.getBytes());
|
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
|
||||||
dict.put(PROP_KEY_OUTBOXID, validMailboxPropsUpdate.getOutboxId()
|
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
|
||||||
.getBytes());
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,94 +555,151 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = FormatException.class)
|
||||||
public void testParseEmptyMailboxPropsUpdate() throws Exception {
|
public void testRejectsMailboxUpdateWithEmptyClientSupports()
|
||||||
|
throws Exception {
|
||||||
BdfDictionary emptyPropsDict = new BdfDictionary();
|
BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
clientHelper.parseAndValidateMailboxUpdate(emptyClientSupports,
|
||||||
.parseAndValidateMailboxPropertiesUpdate(emptyPropsDict);
|
emptyServerSupports, emptyPropsDict
|
||||||
assertNull(parsedProps);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseValidMailboxPropsUpdate() throws Exception {
|
public void testParseMailboxUpdateNoMailbox() throws Exception {
|
||||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||||
.parseAndValidateMailboxPropertiesUpdate(
|
MailboxUpdate parsedUpdate = clientHelper.parseAndValidateMailboxUpdate(
|
||||||
getValidMailboxPropsUpdateDict());
|
someClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
assertTrue(mailboxPropertiesUpdateEqual(validMailboxPropsUpdate,
|
assertFalse(parsedUpdate.hasMailbox());
|
||||||
parsedProps));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateOnionNotDecodable()
|
public void testRejectsMailboxUpdateNoMailboxWithSomeServerSupports()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, emptyPropsDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsMailboxUpdateShortSupports() throws Exception {
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(BdfList.of(BdfList.of(1)),
|
||||||
|
emptyServerSupports, new BdfDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsMailboxUpdateLongSupports() throws Exception {
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(
|
||||||
|
BdfList.of(BdfList.of(1, 0, 0)), emptyServerSupports,
|
||||||
|
new BdfDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsMailboxUpdateNonIntSupports() throws Exception {
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(
|
||||||
|
BdfList.of(BdfList.of(1, "0")), emptyServerSupports,
|
||||||
|
new BdfDictionary()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsMailboxUpdateNonListSupports() throws Exception {
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(
|
||||||
|
BdfList.of("non-list"), emptyServerSupports,
|
||||||
|
new BdfDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseValidMailboxUpdateWithMailbox() throws Exception {
|
||||||
|
MailboxUpdate parsedUpdate = clientHelper.parseAndValidateMailboxUpdate(
|
||||||
|
someClientSupports, someServerSupports,
|
||||||
|
getValidMailboxUpdateWithMailboxDict());
|
||||||
|
assertTrue(
|
||||||
|
mailboxUpdateEqual(validMailboxUpdateWithMailbox,
|
||||||
|
parsedUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void rejectsMailboxUpdateWithEmptyServerSupports() throws Exception {
|
||||||
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
emptyServerSupports, getValidMailboxUpdateWithMailboxDict());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsMailboxUpdateOnionNotDecodable() throws Exception {
|
||||||
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
String badOnion = "!" + propsDict.getString(PROP_KEY_ONION)
|
String badOnion = "!" + propsDict.getString(PROP_KEY_ONION)
|
||||||
.substring(1);
|
.substring(1);
|
||||||
propsDict.put(PROP_KEY_ONION, badOnion);
|
propsDict.put(PROP_KEY_ONION, badOnion);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
emptyServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateOnionWrongLength()
|
public void testRejectsMailboxUpdateOnionWrongLength() throws Exception {
|
||||||
throws Exception {
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
|
||||||
String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!";
|
String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!";
|
||||||
propsDict.put(PROP_KEY_ONION, tooLongOnion);
|
propsDict.put(PROP_KEY_ONION, tooLongOnion);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
emptyServerSupports, propsDict
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateInboxIdWrongLength()
|
public void testRejectsMailboxUpdateInboxIdWrongLength() throws Exception {
|
||||||
throws Exception {
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
|
||||||
propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateOutboxIdWrongLength()
|
public void testRejectsMailboxUpdateOutboxIdWrongLength() throws Exception {
|
||||||
throws Exception {
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
|
||||||
propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateAuthTokenWrongLength()
|
public void testRejectsMailboxUpdateAuthTokenWrongLength()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1));
|
propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1));
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateMissingOnion() throws Exception {
|
public void testRejectsMailboxUpdateMissingOnion() throws Exception {
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
propsDict.remove(PROP_KEY_ONION);
|
propsDict.remove(PROP_KEY_ONION);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateMissingAuthToken()
|
public void testRejectsMailboxUpdateMissingAuthToken() throws Exception {
|
||||||
throws Exception {
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
|
||||||
propsDict.remove(PROP_KEY_AUTHTOKEN);
|
propsDict.remove(PROP_KEY_AUTHTOKEN);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateMissingInboxId() throws Exception {
|
public void testRejectsMailboxUpdateMissingInboxId() throws Exception {
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
propsDict.remove(PROP_KEY_INBOXID);
|
propsDict.remove(PROP_KEY_INBOXID);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsMailboxPropsUpdateMissingOutboxId()
|
public void testRejectsMailboxUpdateMissingOutboxId() throws Exception {
|
||||||
throws Exception {
|
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
|
||||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
|
||||||
propsDict.remove(PROP_KEY_OUTBOXID);
|
propsDict.remove(PROP_KEY_OUTBOXID);
|
||||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
|
||||||
|
someServerSupports, propsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import static java.util.Collections.emptyMap;
|
|||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.concurrent.TimeUnit.HOURS;
|
import static java.util.concurrent.TimeUnit.HOURS;
|
||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -94,11 +95,15 @@ import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private static final int BATCH_CAPACITY =
|
||||||
|
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private final Database<Object> database = context.mock(Database.class);
|
private final Database<Object> database = context.mock(Database.class);
|
||||||
private final ShutdownManager shutdownManager =
|
private final ShutdownManager shutdownManager =
|
||||||
@@ -298,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(19).of(database).startTransaction();
|
exactly(25).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(19).of(database).containsContact(txn, contactId);
|
exactly(25).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(19).of(database).abortTransaction(txn);
|
exactly(25).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
@@ -356,6 +361,39 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.getMessageToSend(transaction, contactId, messageId, 123,
|
||||||
|
true));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(true, transaction ->
|
||||||
|
db.getMessagesToAck(transaction, contactId, 123));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(true, transaction ->
|
||||||
|
db.getMessagesToSend(transaction, contactId, 123, 456));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(true, transaction ->
|
||||||
|
db.getUnackedMessagesToSend(transaction, contactId));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getUnackedMessageBytesToSend(transaction, contactId));
|
db.getUnackedMessageBytesToSend(transaction, contactId));
|
||||||
@@ -439,6 +477,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setAckSent(transaction, contactId,
|
||||||
|
singletonList(messageId)));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.setContactAlias(transaction, contactId, alias));
|
db.setContactAlias(transaction, contactId, alias));
|
||||||
@@ -456,6 +503,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setMessagesSent(transaction, contactId,
|
||||||
|
singletonList(messageId), 123));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.setSyncVersions(transaction, contactId, emptyList()));
|
db.setSyncVersions(transaction, contactId, emptyList()));
|
||||||
@@ -918,12 +974,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).getMessagesToSend(txn, contactId,
|
oneOf(database).getMessagesToSend(txn, contactId,
|
||||||
MAX_MESSAGE_LENGTH * 2, maxLatency);
|
BATCH_CAPACITY, maxLatency);
|
||||||
will(returnValue(ids));
|
will(returnValue(ids));
|
||||||
|
// First message
|
||||||
oneOf(database).getMessage(txn, messageId);
|
oneOf(database).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
|
// Second message
|
||||||
oneOf(database).getMessage(txn, messageId1);
|
oneOf(database).getMessage(txn, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||||
@@ -937,7 +995,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
assertEquals(messages, db.generateBatch(transaction, contactId,
|
assertEquals(messages, db.generateBatch(transaction, contactId,
|
||||||
MAX_MESSAGE_LENGTH * 2, maxLatency)));
|
BATCH_CAPACITY, maxLatency)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1001,12 +1059,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).getRequestedMessagesToSend(txn, contactId,
|
oneOf(database).getRequestedMessagesToSend(txn, contactId,
|
||||||
MAX_MESSAGE_LENGTH * 2, maxLatency);
|
BATCH_CAPACITY, maxLatency);
|
||||||
will(returnValue(ids));
|
will(returnValue(ids));
|
||||||
|
// First message
|
||||||
oneOf(database).getMessage(txn, messageId);
|
oneOf(database).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||||
messageId, maxLatency);
|
messageId, maxLatency);
|
||||||
|
// Second message
|
||||||
oneOf(database).getMessage(txn, messageId1);
|
oneOf(database).getMessage(txn, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||||
@@ -1020,7 +1080,73 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
assertEquals(messages, db.generateRequestedBatch(transaction,
|
assertEquals(messages, db.generateRequestedBatch(transaction,
|
||||||
contactId, MAX_MESSAGE_LENGTH * 2, maxLatency)));
|
contactId, BATCH_CAPACITY, maxLatency)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMessageToSendMessageNotVisible() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsContact(txn, contactId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
assertNull(db.getMessageToSend(transaction, contactId,
|
||||||
|
messageId, maxLatency, false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMessageToSendMessageNotMarkedAsSent() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsContact(txn, contactId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).getMessage(txn, messageId);
|
||||||
|
will(returnValue(message));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
assertEquals(message, db.getMessageToSend(transaction,
|
||||||
|
contactId, messageId, maxLatency, false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMessageToSendMessageMarkedAsSent() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsContact(txn, contactId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).getMessage(txn, messageId);
|
||||||
|
will(returnValue(message));
|
||||||
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||||
|
maxLatency);
|
||||||
|
oneOf(database).lowerRequestedFlag(txn, contactId,
|
||||||
|
singletonList(messageId));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
assertEquals(message, db.getMessageToSend(transaction,
|
||||||
|
contactId, messageId, maxLatency, true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1245,6 +1371,62 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.receiveRequest(transaction, contactId, r));
|
db.receiveRequest(transaction, contactId, r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetAckSent() throws Exception {
|
||||||
|
Collection<MessageId> acked = asList(messageId, messageId1);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsContact(txn, contactId);
|
||||||
|
will(returnValue(true));
|
||||||
|
// First message is still visible to the contact - flag lowered
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
|
will(returnValue(true));
|
||||||
|
// Second message is no longer visible - flag not lowered
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(database)
|
||||||
|
.lowerAckFlag(txn, contactId, singletonList(messageId));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setAckSent(transaction, contactId, acked));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetMessagesSent() throws Exception {
|
||||||
|
long maxLatency = 123456;
|
||||||
|
Collection<MessageId> sent = asList(messageId, messageId1);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(database).startTransaction();
|
||||||
|
will(returnValue(txn));
|
||||||
|
oneOf(database).containsContact(txn, contactId);
|
||||||
|
will(returnValue(true));
|
||||||
|
// First message is still visible to the contact - mark as sent
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(database).getMessageLength(txn, messageId);
|
||||||
|
will(returnValue(message.getRawLength()));
|
||||||
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||||
|
maxLatency);
|
||||||
|
// Second message is no longer visible - don't mark as sent
|
||||||
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(database).lowerRequestedFlag(txn, contactId,
|
||||||
|
singletonList(messageId));
|
||||||
|
oneOf(database).commitTransaction(txn);
|
||||||
|
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||||
|
}});
|
||||||
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setMessagesSent(transaction, contactId, sent, maxLatency));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
|
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ import java.util.Random;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.OFF;
|
import static java.util.logging.Level.OFF;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
@@ -97,6 +99,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
// All our transports use a maximum latency of 30 seconds
|
// All our transports use a maximum latency of 30 seconds
|
||||||
private static final int MAX_LATENCY = 30 * 1000;
|
private static final int MAX_LATENCY = 30 * 1000;
|
||||||
|
|
||||||
|
private static final int BATCH_CAPACITY =
|
||||||
|
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
|
||||||
|
|
||||||
protected final File testDir = getTestDirectory();
|
protected final File testDir = getTestDirectory();
|
||||||
private final File resultsFile = new File(getTestName() + ".tsv");
|
private final File resultsFile = new File(getTestName() + ".tsv");
|
||||||
protected final Random random = new Random();
|
protected final Random random = new Random();
|
||||||
@@ -471,7 +476,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
benchmark(name, db -> {
|
benchmark(name, db -> {
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
db.getMessagesToSend(txn, pickRandom(contacts).getId(),
|
db.getMessagesToSend(txn, pickRandom(contacts).getId(),
|
||||||
MAX_MESSAGE_IDS, MAX_LATENCY);
|
BATCH_CAPACITY, MAX_LATENCY);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -522,7 +527,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
benchmark(name, db -> {
|
benchmark(name, db -> {
|
||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
db.getRequestedMessagesToSend(txn, pickRandom(contacts).getId(),
|
db.getRequestedMessagesToSend(txn, pickRandom(contacts).getId(),
|
||||||
MAX_MESSAGE_IDS, MAX_LATENCY);
|
BATCH_CAPACITY, MAX_LATENCY);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singleton;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
@@ -65,6 +64,7 @@ import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADL
|
|||||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -350,14 +350,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// The message is sendable, but too large to send
|
// The message is sendable, but too large to send
|
||||||
assertOneMessageToSendLazily(db, txn);
|
assertOneMessageToSendLazily(db, txn);
|
||||||
assertOneMessageToSendEagerly(db, txn);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
|
long capacity = RECORD_HEADER_BYTES + message.getRawLength() - 1;
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
db.getMessagesToSend(txn, contactId, capacity, MAX_LATENCY);
|
||||||
MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// The message is just the right size to send
|
// The message is just the right size to send
|
||||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
capacity = RECORD_HEADER_BYTES + message.getRawLength();
|
||||||
MAX_LATENCY);
|
ids = db.getMessagesToSend(txn, contactId, capacity, MAX_LATENCY);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -396,16 +396,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
// Remove both message IDs
|
// Lower the ack flag
|
||||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||||
|
|
||||||
// Both message IDs should have been removed
|
// No message IDs should be returned
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
|
||||||
contactId, 1234));
|
|
||||||
|
|
||||||
// Raise the ack flag again
|
// Raise the ack flag again
|
||||||
db.raiseAckFlag(txn, contactId, messageId);
|
db.raiseAckFlag(txn, contactId, messageId);
|
||||||
@@ -2603,7 +2602,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertFalse(
|
assertFalse(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
Map<MessageId, Integer> unacked =
|
Collection<MessageId> unacked =
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
assertTrue(unacked.isEmpty());
|
assertTrue(unacked.isEmpty());
|
||||||
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
|
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
|
||||||
@@ -2613,10 +2612,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Connection txn) throws Exception {
|
Connection txn) throws Exception {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
Map<MessageId, Integer> unacked =
|
Collection<MessageId> unacked =
|
||||||
db.getUnackedMessagesToSend(txn, contactId);
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
assertEquals(singleton(messageId), unacked.keySet());
|
assertEquals(singletonList(messageId), unacked);
|
||||||
assertEquals(message.getRawLength(), unacked.get(messageId).intValue());
|
|
||||||
assertEquals(message.getRawLength(),
|
assertEquals(message.getRawLength(),
|
||||||
db.getUnackedMessageBytesToSend(txn, contactId));
|
db.getUnackedMessageBytesToSend(txn, contactId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.mailbox.ConnectivityCheckerImpl.CONNECTIVITY_CHECK_FRESHNESS_MS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
|
||||||
|
public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final MailboxApiCaller mailboxApiCaller =
|
||||||
|
context.mock(MailboxApiCaller.class);
|
||||||
|
private final ApiCall apiCall = context.mock(ApiCall.class);
|
||||||
|
private final Cancellable task = context.mock(Cancellable.class);
|
||||||
|
private final ConnectivityObserver observer1 =
|
||||||
|
context.mock(ConnectivityObserver.class, "1");
|
||||||
|
private final ConnectivityObserver observer2 =
|
||||||
|
context.mock(ConnectivityObserver.class, "2");
|
||||||
|
|
||||||
|
private final MailboxProperties properties =
|
||||||
|
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||||
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFirstObserverStartsCheck() {
|
||||||
|
ConnectivityCheckerImpl checker = createChecker();
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer1);
|
||||||
|
|
||||||
|
// When the check succeeds the observer should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(observer1).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
|
||||||
|
// The observer should not be called again when subsequent checks
|
||||||
|
// succeed
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObserverIsAddedToExistingCheck() {
|
||||||
|
ConnectivityCheckerImpl checker = createChecker();
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer1);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called again before the first check
|
||||||
|
// succeeds, the observer should be added to the existing check
|
||||||
|
checker.checkConnectivity(properties, observer2);
|
||||||
|
|
||||||
|
// When the check succeeds both observers should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(observer1).onConnectivityCheckSucceeded();
|
||||||
|
oneOf(observer2).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
|
||||||
|
// The observers should not be called again when subsequent checks
|
||||||
|
// succeed
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFreshResultIsReused() {
|
||||||
|
ConnectivityCheckerImpl checker = createChecker();
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer1);
|
||||||
|
|
||||||
|
// When the check succeeds the observer should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(observer1).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called again within
|
||||||
|
// CONNECTIVITY_CHECK_FRESHNESS_MS the observer should be called with
|
||||||
|
// the result of the recent check
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS));
|
||||||
|
oneOf(observer2).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStaleResultIsNotReused() {
|
||||||
|
ConnectivityCheckerImpl checker = createChecker();
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer1);
|
||||||
|
|
||||||
|
// When the check succeeds the observer should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(observer1).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called again after more than
|
||||||
|
// CONNECTIVITY_CHECK_FRESHNESS_MS another check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer2);
|
||||||
|
|
||||||
|
// When the check succeeds the observer should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(observer2).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.onConnectivityCheckSucceeded(
|
||||||
|
now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckIsCancelledWhenCheckerIsDestroyed() {
|
||||||
|
ConnectivityCheckerImpl checker = createChecker();
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
|
||||||
|
will(returnValue(task));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer1);
|
||||||
|
|
||||||
|
// When the checker is destroyed the check should be cancelled
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(task).cancel();
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.destroy();
|
||||||
|
|
||||||
|
// If the check runs anyway (cancellation came too late) the observer
|
||||||
|
// should not be called
|
||||||
|
checker.onConnectivityCheckSucceeded(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectivityCheckerImpl createChecker() {
|
||||||
|
|
||||||
|
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
protected ApiCall createConnectivityCheckTask(
|
||||||
|
@Nonnull MailboxProperties properties) {
|
||||||
|
return apiCall;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.lib.action.DoAllAction;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class ContactMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final MailboxApiCaller mailboxApiCaller =
|
||||||
|
context.mock(MailboxApiCaller.class);
|
||||||
|
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||||
|
private final Cancellable task = context.mock(Cancellable.class);
|
||||||
|
private final ConnectivityObserver observer =
|
||||||
|
context.mock(ConnectivityObserver.class);
|
||||||
|
|
||||||
|
private final MailboxProperties properties =
|
||||||
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
|
||||||
|
ContactMailboxConnectivityChecker checker = createChecker();
|
||||||
|
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
|
||||||
|
returnValue(task)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer);
|
||||||
|
|
||||||
|
// When the check succeeds the observer should be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxApi).checkStatus(properties);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(observer).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
// The call should not be retried
|
||||||
|
assertFalse(apiCall.get().callApi());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObserverIsNotCalledWhenCheckFails() throws Exception {
|
||||||
|
ContactMailboxConnectivityChecker checker = createChecker();
|
||||||
|
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
|
||||||
|
returnValue(task)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer);
|
||||||
|
|
||||||
|
// When the check fails, the observer should not be called
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(mailboxApi).checkStatus(properties);
|
||||||
|
will(throwException(new IOException()));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// The call should be retried
|
||||||
|
assertTrue(apiCall.get().callApi());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactMailboxConnectivityChecker createChecker() {
|
||||||
|
return new ContactMailboxConnectivityChecker(clock, mailboxApiCaller,
|
||||||
|
mailboxApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.lib.action.DoAllAction;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApiCaller.MAX_RETRY_INTERVAL_MS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApiCaller.MIN_RETRY_INTERVAL_MS;
|
||||||
|
|
||||||
|
public class MailboxApiCallerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final TaskScheduler taskScheduler =
|
||||||
|
context.mock(TaskScheduler.class);
|
||||||
|
private final Executor ioExecutor = context.mock(Executor.class);
|
||||||
|
private final ApiCall apiCall = context.mock(ApiCall.class);
|
||||||
|
private final Cancellable scheduledTask = context.mock(Cancellable.class);
|
||||||
|
|
||||||
|
private final MailboxApiCallerImpl caller =
|
||||||
|
new MailboxApiCallerImpl(taskScheduler, ioExecutor);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubmitsTaskImmediately() {
|
||||||
|
// Calling retryWithBackoff() should schedule the first try immediately
|
||||||
|
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||||
|
will(new CaptureArgumentAction<>(runnable, Runnable.class, 0));
|
||||||
|
}});
|
||||||
|
|
||||||
|
caller.retryWithBackoff(apiCall);
|
||||||
|
|
||||||
|
// When the task runs, the API call should be called. The call
|
||||||
|
// returns false, so no retries should be scheduled
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(apiCall).callApi();
|
||||||
|
will(returnValue(false));
|
||||||
|
}});
|
||||||
|
|
||||||
|
runnable.get().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotRetryTaskIfCancelled() {
|
||||||
|
// Calling retryWithBackoff() should schedule the first try immediately
|
||||||
|
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||||
|
will(new CaptureArgumentAction<>(runnable, Runnable.class, 0));
|
||||||
|
}});
|
||||||
|
|
||||||
|
Cancellable returned = caller.retryWithBackoff(apiCall);
|
||||||
|
|
||||||
|
// When the task runs, the API call should be called. The call
|
||||||
|
// returns true, so a retry should be scheduled
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(apiCall).callApi();
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with(ioExecutor), with(MIN_RETRY_INTERVAL_MS),
|
||||||
|
with(MILLISECONDS));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
|
||||||
|
returnValue(scheduledTask)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
runnable.get().run();
|
||||||
|
|
||||||
|
// When the Cancellable returned by retryWithBackoff() is cancelled,
|
||||||
|
// the scheduled task should be cancelled
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(scheduledTask).cancel();
|
||||||
|
}});
|
||||||
|
|
||||||
|
returned.cancel();
|
||||||
|
|
||||||
|
// Cancelling again should have no effect
|
||||||
|
returned.cancel();
|
||||||
|
|
||||||
|
// If the scheduled task runs anyway (cancellation came too late),
|
||||||
|
// the API call should not be called and no further tries should be
|
||||||
|
// scheduled
|
||||||
|
runnable.get().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoublesRetryIntervalUntilMaximumIsReached() {
|
||||||
|
// The expected retry intervals increase from the min to the max
|
||||||
|
List<Long> expectedIntervals = new ArrayList<>();
|
||||||
|
for (long interval = MIN_RETRY_INTERVAL_MS;
|
||||||
|
interval <= MAX_RETRY_INTERVAL_MS; interval *= 2) {
|
||||||
|
expectedIntervals.add(interval);
|
||||||
|
}
|
||||||
|
// Once the interval reaches the maximum it should be capped
|
||||||
|
expectedIntervals.add(MAX_RETRY_INTERVAL_MS);
|
||||||
|
expectedIntervals.add(MAX_RETRY_INTERVAL_MS);
|
||||||
|
|
||||||
|
// Calling retryWithBackoff() should schedule the first try immediately
|
||||||
|
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(ioExecutor).execute(with(any(Runnable.class)));
|
||||||
|
will(new CaptureArgumentAction<>(runnable, Runnable.class, 0));
|
||||||
|
}});
|
||||||
|
|
||||||
|
caller.retryWithBackoff(apiCall);
|
||||||
|
|
||||||
|
// Each time the task runs, the API call returns true, so a retry
|
||||||
|
// should be scheduled with a longer interval
|
||||||
|
for (long interval : expectedIntervals) {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(apiCall).callApi();
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with(ioExecutor), with(interval), with(MILLISECONDS));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(
|
||||||
|
runnable, Runnable.class, 0),
|
||||||
|
returnValue(scheduledTask)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
runnable.get().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxId;
|
import org.briarproject.bramble.api.mailbox.MailboxId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||||
@@ -34,9 +35,12 @@ import okio.Buffer;
|
|||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesEqual;
|
||||||
import static org.briarproject.bramble.test.TestUtils.readBytes;
|
import static org.briarproject.bramble.test.TestUtils.readBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.writeBytes;
|
import static org.briarproject.bramble.test.TestUtils.writeBytes;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
@@ -79,12 +83,109 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
contactId, contactToken, contactInboxId, contactOutboxId);
|
contactId, contactToken, contactInboxId, contactOutboxId);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetup() throws Exception {
|
public void testGetServerSupports() throws Exception {
|
||||||
String validResponse = "{\"token\":\"" + token2 + "\"}";
|
String validVersions = "[ {\"major\":1,\"minor\":0} ]";
|
||||||
|
String validResponse = makeVersionsResponse(validVersions);
|
||||||
String invalidResponse = "{\"foo\":\"bar\"}";
|
String invalidResponse = "{\"foo\":\"bar\"}";
|
||||||
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}";
|
String invalidVersionsResponse = makeVersionsResponse("42");
|
||||||
String invalidTokenResponse2 =
|
String invalidVersionsResponse2 = makeVersionsResponse("[ 1,0 ]");
|
||||||
"{\"token\":\"" + getRandomString(64) + "\"}";
|
String invalidVersionsResponse3 =
|
||||||
|
makeVersionsResponse("[ {\"major\":1, \"minor\":-1} ]");
|
||||||
|
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
server.enqueue(new MockResponse().setBody(validResponse));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidResponse));
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(401));
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse2));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse3));
|
||||||
|
server.start();
|
||||||
|
String baseUrl = getBaseUrl(server);
|
||||||
|
List<MailboxVersion> versions = singletonList(new MailboxVersion(1, 0));
|
||||||
|
MailboxProperties properties =
|
||||||
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
MailboxProperties properties2 =
|
||||||
|
new MailboxProperties(baseUrl, token2, new ArrayList<>());
|
||||||
|
|
||||||
|
RecordedRequest request;
|
||||||
|
|
||||||
|
// valid response with valid token
|
||||||
|
assertEquals(versions, api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
|
||||||
|
// invalid response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
|
||||||
|
// 401 response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties2));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token2);
|
||||||
|
|
||||||
|
// 500 response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
|
||||||
|
// invalid non-array serverSupports response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
|
||||||
|
// invalid non-object in serverSupports array response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
|
||||||
|
// invalid negative minor version in serverSupports response
|
||||||
|
assertThrows(ApiException.class,
|
||||||
|
() -> api.getServerSupports(properties));
|
||||||
|
request = server.takeRequest();
|
||||||
|
assertEquals("/versions", request.getPath());
|
||||||
|
assertEquals("GET", request.getMethod());
|
||||||
|
assertToken(request, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeVersionsResponse(String versions) {
|
||||||
|
return "{\"serverSupports\":" + versions + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetup() throws Exception {
|
||||||
|
String validVersions = "[ {\"major\":1,\"minor\":0} ]";
|
||||||
|
String validResponse = makeSetupResponse(
|
||||||
|
"\"" + token2 + "\"", validVersions);
|
||||||
|
String invalidResponse = "{\"foo\":\"bar\"}";
|
||||||
|
String invalidTokenResponse = makeSetupResponse(
|
||||||
|
"{\"foo\":\"bar\"}", validVersions);
|
||||||
|
String invalidTokenResponse2 = makeSetupResponse(
|
||||||
|
"\"" + getRandomString(64) + "\"", validVersions);
|
||||||
|
String invalidVersionsResponse = makeSetupResponse(
|
||||||
|
"\"" + token2 + "\"", "42");
|
||||||
|
String invalidVersionsResponse2 = makeSetupResponse(
|
||||||
|
"\"" + token2 + "\"", "[ 1,0 ]");
|
||||||
|
String invalidVersionsResponse3 = makeSetupResponse(
|
||||||
|
"\"" + token2 + "\"", "[ {\"major\":1, \"minor\":-1} ]");
|
||||||
|
|
||||||
MockWebServer server = new MockWebServer();
|
MockWebServer server = new MockWebServer();
|
||||||
server.enqueue(new MockResponse().setBody(validResponse));
|
server.enqueue(new MockResponse().setBody(validResponse));
|
||||||
@@ -94,15 +195,18 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.enqueue(new MockResponse().setResponseCode(500));
|
server.enqueue(new MockResponse().setResponseCode(500));
|
||||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
|
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
|
||||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
|
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse2));
|
||||||
|
server.enqueue(new MockResponse().setBody(invalidVersionsResponse3));
|
||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
MailboxProperties properties2 =
|
MailboxProperties properties2 =
|
||||||
new MailboxProperties(baseUrl, token2, true);
|
new MailboxProperties(baseUrl, token2, new ArrayList<>());
|
||||||
|
|
||||||
// valid response with valid token
|
// valid response with valid token
|
||||||
assertEquals(token2, api.setup(properties));
|
mailboxPropertiesEqual(properties2, api.setup(properties));
|
||||||
RecordedRequest request1 = server.takeRequest();
|
RecordedRequest request1 = server.takeRequest();
|
||||||
assertEquals("/setup", request1.getPath());
|
assertEquals("/setup", request1.getPath());
|
||||||
assertEquals("PUT", request1.getMethod());
|
assertEquals("PUT", request1.getMethod());
|
||||||
@@ -149,12 +253,38 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
assertEquals("/setup", request7.getPath());
|
assertEquals("/setup", request7.getPath());
|
||||||
assertEquals("PUT", request7.getMethod());
|
assertEquals("PUT", request7.getMethod());
|
||||||
assertToken(request7, token);
|
assertToken(request7, token);
|
||||||
|
|
||||||
|
// invalid non-array serverSupports response
|
||||||
|
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||||
|
RecordedRequest request8 = server.takeRequest();
|
||||||
|
assertEquals("/setup", request8.getPath());
|
||||||
|
assertEquals("PUT", request8.getMethod());
|
||||||
|
assertToken(request8, token);
|
||||||
|
|
||||||
|
// invalid non-object in serverSupports array response
|
||||||
|
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||||
|
RecordedRequest request9 = server.takeRequest();
|
||||||
|
assertEquals("/setup", request9.getPath());
|
||||||
|
assertEquals("PUT", request9.getMethod());
|
||||||
|
assertToken(request9, token);
|
||||||
|
|
||||||
|
// invalid negative minor version in serverSupports response
|
||||||
|
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||||
|
RecordedRequest request10 = server.takeRequest();
|
||||||
|
assertEquals("/setup", request10.getPath());
|
||||||
|
assertEquals("PUT", request10.getMethod());
|
||||||
|
assertToken(request10, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeSetupResponse(String token, String versions) {
|
||||||
|
return "{\"token\":" + token + "," +
|
||||||
|
"\"serverSupports\":" + versions + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetupOnlyForOwner() {
|
public void testSetupOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalArgumentException.class,
|
IllegalArgumentException.class,
|
||||||
() -> api.setup(properties)
|
() -> api.setup(properties)
|
||||||
@@ -170,9 +300,9 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
MailboxProperties properties2 =
|
MailboxProperties properties2 =
|
||||||
new MailboxProperties(baseUrl, token2, true);
|
new MailboxProperties(baseUrl, token2, new ArrayList<>());
|
||||||
|
|
||||||
assertTrue(api.checkStatus(properties));
|
assertTrue(api.checkStatus(properties));
|
||||||
RecordedRequest request1 = server.takeRequest();
|
RecordedRequest request1 = server.takeRequest();
|
||||||
@@ -190,16 +320,6 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
assertToken(request3, token);
|
assertToken(request3, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStatusOnlyForOwner() {
|
|
||||||
MailboxProperties properties =
|
|
||||||
new MailboxProperties("", token, false);
|
|
||||||
assertThrows(
|
|
||||||
IllegalArgumentException.class,
|
|
||||||
() -> api.checkStatus(properties)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWipe() throws Exception {
|
public void testWipe() throws Exception {
|
||||||
MockWebServer server = new MockWebServer();
|
MockWebServer server = new MockWebServer();
|
||||||
@@ -210,9 +330,9 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
MailboxProperties properties2 =
|
MailboxProperties properties2 =
|
||||||
new MailboxProperties(baseUrl, token2, true);
|
new MailboxProperties(baseUrl, token2, new ArrayList<>());
|
||||||
|
|
||||||
api.wipeMailbox(properties);
|
api.wipeMailbox(properties);
|
||||||
RecordedRequest request1 = server.takeRequest();
|
RecordedRequest request1 = server.takeRequest();
|
||||||
@@ -242,7 +362,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testWipeOnlyForOwner() {
|
public void testWipeOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
api.wipeMailbox(properties));
|
api.wipeMailbox(properties));
|
||||||
}
|
}
|
||||||
@@ -256,7 +376,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// contact gets added as expected
|
// contact gets added as expected
|
||||||
api.addContact(properties, mailboxContact);
|
api.addContact(properties, mailboxContact);
|
||||||
@@ -288,7 +408,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testAddContactOnlyForOwner() {
|
public void testAddContactOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
api.addContact(properties, mailboxContact));
|
api.addContact(properties, mailboxContact));
|
||||||
}
|
}
|
||||||
@@ -303,7 +423,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// contact gets deleted as expected
|
// contact gets deleted as expected
|
||||||
api.deleteContact(properties, contactId);
|
api.deleteContact(properties, contactId);
|
||||||
@@ -340,7 +460,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testDeleteContactOnlyForOwner() {
|
public void testDeleteContactOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
api.deleteContact(properties, contactId));
|
api.deleteContact(properties, contactId));
|
||||||
}
|
}
|
||||||
@@ -367,7 +487,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// valid response with two contacts
|
// valid response with two contacts
|
||||||
assertEquals(singletonList(contactId), api.getContacts(properties));
|
assertEquals(singletonList(contactId), api.getContacts(properties));
|
||||||
@@ -432,7 +552,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetContactsOnlyForOwner() {
|
public void testGetContactsOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(
|
assertThrows(
|
||||||
IllegalArgumentException.class,
|
IllegalArgumentException.class,
|
||||||
() -> api.getContacts(properties)
|
() -> api.getContacts(properties)
|
||||||
@@ -452,7 +572,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// file gets uploaded as expected
|
// file gets uploaded as expected
|
||||||
api.addFile(properties, contactInboxId, file);
|
api.addFile(properties, contactInboxId, file);
|
||||||
@@ -511,7 +631,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// valid response with one file
|
// valid response with one file
|
||||||
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
|
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
|
||||||
@@ -607,7 +727,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// file gets downloaded as expected
|
// file gets downloaded as expected
|
||||||
api.getFile(properties, contactOutboxId, name, file1);
|
api.getFile(properties, contactOutboxId, name, file1);
|
||||||
@@ -651,7 +771,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// file gets deleted as expected
|
// file gets deleted as expected
|
||||||
api.deleteFile(properties, contactInboxId, name);
|
api.deleteFile(properties, contactInboxId, name);
|
||||||
@@ -715,7 +835,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
server.start();
|
server.start();
|
||||||
String baseUrl = getBaseUrl(server);
|
String baseUrl = getBaseUrl(server);
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties(baseUrl, token, true);
|
new MailboxProperties(baseUrl, token, new ArrayList<>());
|
||||||
|
|
||||||
// valid response with one folders
|
// valid response with one folders
|
||||||
assertEquals(singletonList(id1), api.getFolders(properties));
|
assertEquals(singletonList(id1), api.getFolders(properties));
|
||||||
@@ -784,7 +904,7 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetFoldersOnlyForOwner() {
|
public void testGetFoldersOnlyForOwner() {
|
||||||
MailboxProperties properties =
|
MailboxProperties properties =
|
||||||
new MailboxProperties("", token, false);
|
getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
api.getFolders(properties));
|
api.getFolders(properties));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.junit.rules.TemporaryFolder;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -89,17 +90,16 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
|
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
|
||||||
|
|
||||||
if (ownerProperties != null) return;
|
if (ownerProperties != null) return;
|
||||||
MailboxProperties setupProperties =
|
MailboxProperties setupProperties = new MailboxProperties(
|
||||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
URL_BASE, SETUP_TOKEN, new ArrayList<>());
|
||||||
MailboxAuthToken ownerToken = api.setup(setupProperties);
|
ownerProperties = api.setup(setupProperties);
|
||||||
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
// we can't test wiping as a regular test as it stops the mailbox
|
// we can't test wiping as a regular test as it stops the mailbox
|
||||||
public static void wipe() throws IOException, ApiException {
|
public static void wipe() throws IOException, ApiException {
|
||||||
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
|
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
|
||||||
|
|
||||||
api.wipeMailbox(ownerProperties);
|
api.wipeMailbox(ownerProperties);
|
||||||
|
|
||||||
// check doesn't work anymore
|
// check doesn't work anymore
|
||||||
@@ -107,14 +107,30 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
api.checkStatus(ownerProperties));
|
api.checkStatus(ownerProperties));
|
||||||
|
|
||||||
// new setup doesn't work as mailbox is stopping
|
// new setup doesn't work as mailbox is stopping
|
||||||
MailboxProperties setupProperties =
|
MailboxProperties setupProperties = new MailboxProperties(
|
||||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
URL_BASE, SETUP_TOKEN, new ArrayList<>());
|
||||||
assertThrows(ApiException.class, () -> api.setup(setupProperties));
|
assertThrows(ApiException.class, () -> api.setup(setupProperties));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStatus() throws Exception {
|
public void testStatus() throws Exception {
|
||||||
|
// Owner calls status endpoint
|
||||||
assertTrue(api.checkStatus(ownerProperties));
|
assertTrue(api.checkStatus(ownerProperties));
|
||||||
|
|
||||||
|
// Owner adds contact
|
||||||
|
ContactId contactId = new ContactId(1);
|
||||||
|
MailboxContact contact = getMailboxContact(contactId);
|
||||||
|
MailboxProperties contactProperties = new MailboxProperties(
|
||||||
|
ownerProperties.getBaseUrl(), contact.token,
|
||||||
|
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
||||||
|
api.addContact(ownerProperties, contact);
|
||||||
|
|
||||||
|
// Contact calls status endpoint
|
||||||
|
assertTrue(api.checkStatus(contactProperties));
|
||||||
|
|
||||||
|
// Owner deletes contact again to leave clean state for other tests
|
||||||
|
api.deleteContact(ownerProperties, contactId);
|
||||||
|
assertEquals(emptyList(), api.getContacts(ownerProperties));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -151,7 +167,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
|
|||||||
ContactId contactId = new ContactId(1);
|
ContactId contactId = new ContactId(1);
|
||||||
MailboxContact contact = getMailboxContact(contactId);
|
MailboxContact contact = getMailboxContact(contactId);
|
||||||
MailboxProperties contactProperties = new MailboxProperties(
|
MailboxProperties contactProperties = new MailboxProperties(
|
||||||
ownerProperties.getBaseUrl(), contact.token, false);
|
ownerProperties.getBaseUrl(), contact.token,
|
||||||
|
new ArrayList<>(), contact.inboxId, contact.outboxId);
|
||||||
api.addContact(ownerProperties, contact);
|
api.addContact(ownerProperties, contact);
|
||||||
|
|
||||||
// upload a file for our contact
|
// upload a file for our contact
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
@@ -23,10 +25,11 @@ import org.junit.Test;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
@@ -46,25 +49,25 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
private final MailboxApi api = context.mock(MailboxApi.class);
|
private final MailboxApi api = context.mock(MailboxApi.class);
|
||||||
private final MailboxSettingsManager mailboxSettingsManager =
|
private final MailboxSettingsManager mailboxSettingsManager =
|
||||||
context.mock(MailboxSettingsManager.class);
|
context.mock(MailboxSettingsManager.class);
|
||||||
private final MailboxPropertyManager mailboxPropertyManager =
|
private final MailboxUpdateManager mailboxUpdateManager =
|
||||||
context.mock(MailboxPropertyManager.class);
|
context.mock(MailboxUpdateManager.class);
|
||||||
private final MailboxPairingTaskFactory factory =
|
private final MailboxPairingTaskFactory factory =
|
||||||
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
|
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
|
||||||
mailboxSettingsManager, mailboxPropertyManager);
|
mailboxSettingsManager, mailboxUpdateManager);
|
||||||
|
|
||||||
private final String onion = getRandomString(56);
|
private final String onion = getRandomString(56);
|
||||||
private final byte[] onionBytes = getRandomBytes(32);
|
private final byte[] onionBytes = getRandomBytes(32);
|
||||||
private final String onionAddress = "http://" + onion + ".onion";
|
private final String baseUrl = "http://" + onion + ".onion"; // TODO
|
||||||
private final MailboxAuthToken setupToken =
|
private final MailboxAuthToken setupToken =
|
||||||
new MailboxAuthToken(getRandomId());
|
new MailboxAuthToken(getRandomId());
|
||||||
private final MailboxAuthToken ownerToken =
|
private final MailboxAuthToken ownerToken =
|
||||||
new MailboxAuthToken(getRandomId());
|
new MailboxAuthToken(getRandomId());
|
||||||
private final String validPayload = getValidPayload();
|
private final String validPayload = getValidPayload();
|
||||||
private final long time = System.currentTimeMillis();
|
private final long time = System.currentTimeMillis();
|
||||||
private final MailboxProperties setupProperties =
|
private final MailboxProperties setupProperties = new MailboxProperties(
|
||||||
new MailboxProperties(onionAddress, setupToken, true);
|
baseUrl, setupToken, new ArrayList<>());
|
||||||
private final MailboxProperties ownerProperties =
|
private final MailboxProperties ownerProperties = new MailboxProperties(
|
||||||
new MailboxProperties(onionAddress, ownerToken, true);
|
baseUrl, ownerToken, new ArrayList<>());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitialQrCodeReceivedState() {
|
public void testInitialQrCodeReceivedState() {
|
||||||
@@ -98,22 +101,24 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(crypto).encodeOnion(onionBytes);
|
oneOf(crypto).encodeOnion(onionBytes);
|
||||||
will(returnValue(onion));
|
will(returnValue(onion));
|
||||||
oneOf(api).setup(with(matches(setupProperties)));
|
oneOf(api).setup(with(matches(setupProperties)));
|
||||||
will(returnValue(ownerToken));
|
will(returnValue(ownerProperties));
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(time));
|
will(returnValue(time));
|
||||||
}});
|
}});
|
||||||
Contact contact1 = getContact();
|
Contact contact1 = getContact();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
MailboxUpdate updateNoMailbox = new MailboxUpdate(
|
||||||
|
singletonList(new MailboxVersion(47, 11)));
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
||||||
with(txn), with(matches(ownerProperties)));
|
with(txn), with(matches(ownerProperties)));
|
||||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
|
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(Collections.singletonList(contact1)));
|
will(returnValue(singletonList(contact1)));
|
||||||
oneOf(mailboxPropertyManager).getRemoteProperties(txn,
|
oneOf(mailboxUpdateManager).getRemoteUpdate(txn,
|
||||||
contact1.getId());
|
contact1.getId());
|
||||||
will(returnValue(null));
|
will(returnValue(updateNoMailbox));
|
||||||
oneOf(db).resetUnackedMessagesToSend(txn, contact1.getId());
|
oneOf(db).resetUnackedMessagesToSend(txn, contact1.getId());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -174,7 +179,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(crypto).encodeOnion(onionBytes);
|
oneOf(crypto).encodeOnion(onionBytes);
|
||||||
will(returnValue(onion));
|
will(returnValue(onion));
|
||||||
oneOf(api).setup(with(matches(setupProperties)));
|
oneOf(api).setup(with(matches(setupProperties)));
|
||||||
will(returnValue(ownerToken));
|
will(returnValue(ownerProperties));
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(time));
|
will(returnValue(time));
|
||||||
}});
|
}});
|
||||||
@@ -206,7 +211,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
|||||||
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
||||||
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
||||||
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
|
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
|
||||||
p1.isOwner() == p2.isOwner());
|
p1.isOwner() == p2.isOwner() &&
|
||||||
|
p1.getServerSupports().equals(p2.getServerSupports()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
|
||||||
import org.jmock.Expectations;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
public class MailboxPropertyValidatorTest extends BrambleMockTestCase {
|
|
||||||
|
|
||||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
|
||||||
|
|
||||||
private final BdfDictionary bdfDict;
|
|
||||||
private final MailboxPropertiesUpdate mailboxProps;
|
|
||||||
private final Group group;
|
|
||||||
private final Message message;
|
|
||||||
private final MailboxPropertyValidator mpv;
|
|
||||||
|
|
||||||
public MailboxPropertyValidatorTest() {
|
|
||||||
// Just dummies, clientHelper is mocked so our test is a bit shallow;
|
|
||||||
// not testing
|
|
||||||
// {@link ClientHelper#parseAndValidateMailboxPropertiesUpdate(BdfDictionary)}
|
|
||||||
bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
|
||||||
mailboxProps = new MailboxPropertiesUpdate("baz",
|
|
||||||
new MailboxAuthToken(getRandomId()),
|
|
||||||
new MailboxFolderId(getRandomId()),
|
|
||||||
new MailboxFolderId(getRandomId()));
|
|
||||||
|
|
||||||
group = getGroup(MailboxPropertyManager.CLIENT_ID,
|
|
||||||
MailboxPropertyManager.MAJOR_VERSION);
|
|
||||||
message = getMessage(group.getId());
|
|
||||||
|
|
||||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
mpv = new MailboxPropertyValidator(clientHelper, metadataEncoder,
|
|
||||||
clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidateMessageBody() throws IOException {
|
|
||||||
BdfList body = BdfList.of(4, bdfDict);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
|
||||||
bdfDict);
|
|
||||||
will(returnValue(mailboxProps));
|
|
||||||
}});
|
|
||||||
|
|
||||||
BdfDictionary result =
|
|
||||||
mpv.validateMessage(message, group, body).getDictionary();
|
|
||||||
assertEquals(4, result.getLong("version").longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testValidateWrongVersionValue() throws IOException {
|
|
||||||
BdfList body = BdfList.of(-1, bdfDict);
|
|
||||||
mpv.validateMessage(message, group, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = FormatException.class)
|
|
||||||
public void testValidateWrongVersionType() throws IOException {
|
|
||||||
BdfList body = BdfList.of(bdfDict, bdfDict);
|
|
||||||
mpv.validateMessage(message, group, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEmptyPropertiesReturnsNull() throws IOException {
|
|
||||||
BdfDictionary emptyBdfDict = new BdfDictionary();
|
|
||||||
BdfList body = BdfList.of(42, emptyBdfDict);
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
|
||||||
emptyBdfDict);
|
|
||||||
will(returnValue(null));
|
|
||||||
}});
|
|
||||||
|
|
||||||
BdfDictionary result =
|
|
||||||
mpv.validateMessage(message, group, body).getDictionary();
|
|
||||||
assertEquals(42, result.getLong("version").longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,19 +6,23 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_SERVER_SUPPORTS;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
|
||||||
@@ -40,6 +44,9 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
private final String onion = getRandomString(64);
|
private final String onion = getRandomString(64);
|
||||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||||
|
private final List<MailboxVersion> serverSupports =
|
||||||
|
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
|
||||||
|
private final int[] serverSupportsInts = {1, 0, 1, 1};
|
||||||
private final ContactId contactId1 = new ContactId(random.nextInt());
|
private final ContactId contactId1 = new ContactId(random.nextInt());
|
||||||
private final ContactId contactId2 = new ContactId(random.nextInt());
|
private final ContactId contactId2 = new ContactId(random.nextInt());
|
||||||
private final ContactId contactId3 = new ContactId(random.nextInt());
|
private final ContactId contactId3 = new ContactId(random.nextInt());
|
||||||
@@ -67,6 +74,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
Settings settings = new Settings();
|
Settings settings = new Settings();
|
||||||
settings.put(SETTINGS_KEY_ONION, onion);
|
settings.put(SETTINGS_KEY_ONION, onion);
|
||||||
settings.put(SETTINGS_KEY_TOKEN, token.toString());
|
settings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||||
|
settings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
@@ -77,6 +85,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertNotNull(properties);
|
assertNotNull(properties);
|
||||||
assertEquals(onion, properties.getBaseUrl());
|
assertEquals(onion, properties.getBaseUrl());
|
||||||
assertEquals(token, properties.getAuthToken());
|
assertEquals(token, properties.getAuthToken());
|
||||||
|
assertEquals(serverSupports, properties.getServerSupports());
|
||||||
assertTrue(properties.isOwner());
|
assertTrue(properties.isOwner());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +95,10 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
|||||||
Settings expectedSettings = new Settings();
|
Settings expectedSettings = new Settings();
|
||||||
expectedSettings.put(SETTINGS_KEY_ONION, onion);
|
expectedSettings.put(SETTINGS_KEY_ONION, onion);
|
||||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||||
MailboxProperties properties =
|
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
||||||
new MailboxProperties(onion, token, true);
|
serverSupportsInts);
|
||||||
|
MailboxProperties properties = new MailboxProperties(onion, token,
|
||||||
|
serverSupports);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import org.briarproject.bramble.api.data.MetadataParser;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
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.event.RemoteMailboxUpdateEvent;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
@@ -30,30 +30,33 @@ import org.junit.Test;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_CLIENT_SUPPORTS;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
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.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
import static org.briarproject.bramble.test.TestUtils.mailboxUpdateEqual;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||||
@@ -71,32 +74,62 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final BdfDictionary propsDict;
|
private final BdfDictionary propsDict;
|
||||||
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||||
private final MailboxPropertiesUpdate props;
|
private final List<MailboxVersion> someClientSupportsList;
|
||||||
|
private final BdfList someClientSupports;
|
||||||
|
private final List<MailboxVersion> newerClientSupportsList;
|
||||||
|
private final BdfList newerClientSupports;
|
||||||
|
private final List<MailboxVersion> someServerSupportsList;
|
||||||
|
private final BdfList someServerSupports;
|
||||||
|
private final BdfList emptyServerSupports = new BdfList();
|
||||||
|
private final MailboxProperties updateProps;
|
||||||
|
private final MailboxUpdateWithMailbox updateWithMailbox;
|
||||||
|
private final MailboxUpdate updateNoMailbox;
|
||||||
private final MailboxProperties ownProps;
|
private final MailboxProperties ownProps;
|
||||||
|
|
||||||
public MailboxPropertyManagerImplTest() {
|
public MailboxUpdateManagerImplTest() {
|
||||||
ownProps = new MailboxProperties("http://bar.onion",
|
Random rnd = new Random();
|
||||||
new MailboxAuthToken(getRandomId()), true);
|
someClientSupportsList = singletonList(new MailboxVersion(
|
||||||
props = new MailboxPropertiesUpdate(ownProps.getOnion(),
|
rnd.nextInt(), rnd.nextInt()));
|
||||||
new MailboxAuthToken(getRandomId()),
|
someClientSupports = BdfList.of(BdfList.of(
|
||||||
new MailboxFolderId(getRandomId()),
|
someClientSupportsList.get(0).getMajor(),
|
||||||
new MailboxFolderId(getRandomId()));
|
someClientSupportsList.get(0).getMinor()));
|
||||||
|
newerClientSupportsList = singletonList(new MailboxVersion(
|
||||||
|
someClientSupportsList.get(0).getMajor(),
|
||||||
|
someClientSupportsList.get(0).getMinor() + 1));
|
||||||
|
newerClientSupports = BdfList.of(BdfList.of(
|
||||||
|
newerClientSupportsList.get(0).getMajor(),
|
||||||
|
newerClientSupportsList.get(0).getMinor()));
|
||||||
|
|
||||||
|
someServerSupportsList = singletonList(new MailboxVersion(
|
||||||
|
rnd.nextInt(), rnd.nextInt()));
|
||||||
|
someServerSupports = BdfList.of(BdfList.of(
|
||||||
|
someServerSupportsList.get(0).getMajor(),
|
||||||
|
someServerSupportsList.get(0).getMinor()));
|
||||||
|
|
||||||
|
updateNoMailbox = new MailboxUpdate(someClientSupportsList);
|
||||||
|
|
||||||
|
updateProps = getMailboxProperties(false, someServerSupportsList);
|
||||||
|
ownProps = new MailboxProperties(updateProps.getBaseUrl(),
|
||||||
|
updateProps.getAuthToken(), someServerSupportsList);
|
||||||
|
updateWithMailbox = new MailboxUpdateWithMailbox(someClientSupportsList,
|
||||||
|
updateProps);
|
||||||
propsDict = new BdfDictionary();
|
propsDict = new BdfDictionary();
|
||||||
propsDict.put(PROP_KEY_ONION, props.getOnion());
|
propsDict.put(PROP_KEY_ONION, updateProps.getOnion());
|
||||||
propsDict.put(PROP_KEY_AUTHTOKEN, props.getAuthToken().getBytes());
|
propsDict.put(PROP_KEY_AUTHTOKEN, updateProps.getAuthToken());
|
||||||
propsDict.put(PROP_KEY_INBOXID, props.getInboxId().getBytes());
|
propsDict.put(PROP_KEY_INBOXID, updateProps.getInboxId());
|
||||||
propsDict.put(PROP_KEY_OUTBOXID, props.getOutboxId().getBytes());
|
propsDict.put(PROP_KEY_OUTBOXID, updateProps.getOutboxId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private MailboxPropertyManagerImpl createInstance() {
|
private MailboxUpdateManagerImpl createInstance(
|
||||||
|
List<MailboxVersion> clientSupports) {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION);
|
MAJOR_VERSION);
|
||||||
will(returnValue(localGroup));
|
will(returnValue(localGroup));
|
||||||
}});
|
}});
|
||||||
return new MailboxPropertyManagerImpl(db, clientHelper,
|
return new MailboxUpdateManagerImpl(clientSupports, db,
|
||||||
clientVersioningManager, metadataParser, contactGroupFactory,
|
clientHelper, clientVersioningManager, metadataParser,
|
||||||
clock, mailboxSettingsManager, crypto);
|
contactGroupFactory, clock, mailboxSettingsManager, crypto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -104,6 +137,10 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
@@ -111,6 +148,8 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
|
// addingContact()
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
@@ -124,9 +163,20 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
emptyServerSupports, emptyPropsDict, true);
|
||||||
|
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +187,9 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
@@ -144,6 +197,8 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
|
// addingContact()
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
@@ -158,35 +213,201 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getAuthToken()));
|
will(returnValue(updateProps.getAuthToken()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getInboxId()));
|
will(returnValue(updateProps.getInboxId()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getOutboxId()));
|
will(returnValue(updateProps.getOutboxId()));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
someServerSupports, propsDict, true);
|
||||||
|
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
public void testUnchangedClientSupportsOnSecondStartup() throws Exception {
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
Contact contact = getContact();
|
||||||
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
|
||||||
|
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||||
|
new LinkedHashMap<>();
|
||||||
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
someClientSupports));
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
|
oneOf(db).getContacts(txn);
|
||||||
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
|
// addingContact()
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(db).addGroup(txn, contactGroup);
|
||||||
|
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||||
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
|
will(returnValue(SHARED));
|
||||||
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
|
contactGroup.getId(), SHARED);
|
||||||
|
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||||
|
contact.getId());
|
||||||
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
|
will(returnValue(null));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(emptyMessageMetadata));
|
||||||
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
emptyServerSupports, emptyPropsDict, true);
|
||||||
|
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
|
sentDict);
|
||||||
|
}});
|
||||||
|
|
||||||
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
|
t.onDatabaseOpened(txn);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
|
localGroup.getId());
|
||||||
|
will(returnValue(sentDict));
|
||||||
|
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
||||||
|
will(returnValue(someClientSupportsList));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
t = createInstance(someClientSupportsList);
|
||||||
|
t.onDatabaseOpened(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
|
||||||
|
throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
Contact contact = getContact();
|
||||||
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
|
||||||
|
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||||
|
new LinkedHashMap<>();
|
||||||
|
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
|
||||||
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
someClientSupports));
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
|
oneOf(db).getContacts(txn);
|
||||||
|
will(returnValue(singletonList(contact)));
|
||||||
|
|
||||||
|
// addingContact()
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(db).addGroup(txn, contactGroup);
|
||||||
|
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||||
|
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
|
will(returnValue(SHARED));
|
||||||
|
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||||
|
contactGroup.getId(), SHARED);
|
||||||
|
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||||
|
contact.getId());
|
||||||
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
|
will(returnValue(null));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(emptyMessageMetadata));
|
||||||
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
emptyServerSupports, emptyPropsDict, true);
|
||||||
|
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
|
sentDict);
|
||||||
|
}});
|
||||||
|
|
||||||
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
|
t.onDatabaseOpened(txn);
|
||||||
|
|
||||||
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
|
);
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
|
propsDict);
|
||||||
|
BdfDictionary newerSentDict = BdfDictionary.of(new BdfEntry(
|
||||||
|
GROUP_KEY_SENT_CLIENT_SUPPORTS,
|
||||||
|
newerClientSupports));
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(true));
|
||||||
|
|
||||||
|
// Find out that we are now on newerClientSupportsList
|
||||||
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
|
localGroup.getId());
|
||||||
|
will(returnValue(sentDict));
|
||||||
|
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
||||||
|
will(returnValue(someClientSupportsList));
|
||||||
|
|
||||||
|
oneOf(db).getContacts(txn);
|
||||||
|
will(returnValue(singletonList(contact)));
|
||||||
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
|
will(returnValue(contact));
|
||||||
|
|
||||||
|
// getLocalUpdate()
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
|
will(returnValue(body));
|
||||||
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
|
someClientSupports, someServerSupports, propsDict);
|
||||||
|
will(returnValue(updateWithMailbox));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
|
||||||
|
// storeMessageReplaceLatest()
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
expectStoreMessage(txn, contactGroup.getId(), 2,
|
||||||
|
newerClientSupports, someServerSupports, propsDict, true);
|
||||||
|
oneOf(db).removeMessage(txn, messageId);
|
||||||
|
|
||||||
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
|
newerSentDict);
|
||||||
|
}});
|
||||||
|
|
||||||
|
t = createInstance(newerClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +417,7 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Create the group and share it with the contact
|
// Create the group and share it with the contact
|
||||||
@@ -212,9 +434,17 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, contact);
|
||||||
|
will(returnValue(contactGroup));
|
||||||
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
|
contactGroup.getId());
|
||||||
|
will(returnValue(messageMetadata));
|
||||||
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
emptyServerSupports, emptyPropsDict, true);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.addingContact(txn, contact);
|
t.addingContact(txn, contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,21 +472,22 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getAuthToken()));
|
will(returnValue(updateProps.getAuthToken()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getInboxId()));
|
will(returnValue(updateProps.getInboxId()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getOutboxId()));
|
will(returnValue(updateProps.getOutboxId()));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
||||||
|
someServerSupports, propsDict, true);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.addingContact(txn, contact);
|
t.addingContact(txn, contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +504,7 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).removeGroup(txn, contactGroup);
|
oneOf(db).removeGroup(txn, contactGroup);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.removingContact(txn, contact);
|
t.removingContact(txn, contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +515,8 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
GroupId contactGroupId = new GroupId(getRandomId());
|
GroupId contactGroupId = new GroupId(getRandomId());
|
||||||
Message message = getMessage(contactGroupId);
|
Message message = getMessage(contactGroupId);
|
||||||
BdfList body = BdfList.of(1, propsDict);
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
|
propsDict);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||||
@@ -308,16 +540,16 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(contact.getId()));
|
will(returnValue(contact.getId()));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
propsDict);
|
someClientSupports, someServerSupports, propsDict);
|
||||||
will(returnValue(props));
|
will(returnValue(updateWithMailbox));
|
||||||
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -327,7 +559,8 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
GroupId contactGroupId = new GroupId(getRandomId());
|
GroupId contactGroupId = new GroupId(getRandomId());
|
||||||
Message message = getMessage(contactGroupId);
|
Message message = getMessage(contactGroupId);
|
||||||
BdfList body = BdfList.of(1, propsDict);
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
|
propsDict);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry(MSG_KEY_VERSION, 2),
|
new BdfEntry(MSG_KEY_VERSION, 2),
|
||||||
@@ -359,16 +592,16 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(contact.getId()));
|
will(returnValue(contact.getId()));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
propsDict);
|
someClientSupports, someServerSupports, propsDict);
|
||||||
will(returnValue(props));
|
will(returnValue(updateWithMailbox));
|
||||||
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -399,14 +632,14 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||||
t.incomingMessage(txn, message, meta));
|
t.incomingMessage(txn, message, meta));
|
||||||
assertFalse(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
assertFalse(hasEvent(txn, RemoteMailboxUpdateEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesAndStoresLocalPropertiesWithNewVersionOnPairing()
|
public void testCreatesAndStoresLocalUpdateWithNewVersionOnPairing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
List<Contact> contacts = singletonList(contact);
|
List<Contact> contacts = singletonList(contact);
|
||||||
@@ -429,27 +662,28 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(contacts));
|
will(returnValue(contacts));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getAuthToken()));
|
will(returnValue(updateProps.getAuthToken()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getInboxId()));
|
will(returnValue(updateProps.getInboxId()));
|
||||||
oneOf(crypto).generateUniqueId();
|
oneOf(crypto).generateUniqueId();
|
||||||
will(returnValue(props.getOutboxId()));
|
will(returnValue(updateProps.getOutboxId()));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||||
MAJOR_VERSION, contact);
|
MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 2, true);
|
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
||||||
|
someServerSupports, propsDict, true);
|
||||||
oneOf(db).removeMessage(txn, latestId);
|
oneOf(db).removeMessage(txn, latestId);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.mailboxPaired(txn, ownProps.getOnion());
|
t.mailboxPaired(txn, ownProps.getOnion(), someServerSupportsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStoresEmptyLocalPropertiesWithNewVersionOnUnpairing()
|
public void testStoresLocalUpdateNoMailboxWithNewVersionOnUnpairing()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
List<Contact> contacts = singletonList(contact);
|
List<Contact> contacts = singletonList(contact);
|
||||||
@@ -477,18 +711,17 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), emptyPropsDict,
|
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
||||||
2, true);
|
emptyServerSupports, emptyPropsDict, true);
|
||||||
oneOf(db).removeMessage(txn, latestId);
|
oneOf(db).removeMessage(txn, latestId);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.mailboxUnpaired(txn);
|
t.mailboxUnpaired(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRemoteProperties()
|
public void testGetRemoteUpdate() throws Exception {
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
@@ -497,34 +730,34 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
|
propsDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
will(returnValue(contact));
|
will(returnValue(contact));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory)
|
||||||
MAJOR_VERSION, contact);
|
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
propsDict);
|
someClientSupports, someServerSupports, propsDict);
|
||||||
will(returnValue(props));
|
will(returnValue(updateWithMailbox));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
MailboxPropertiesUpdate remote =
|
MailboxUpdate remote = t.getRemoteUpdate(txn, contact.getId());
|
||||||
t.getRemoteProperties(txn, contact.getId());
|
assertTrue(mailboxUpdateEqual(remote, updateWithMailbox));
|
||||||
assertTrue(mailboxPropertiesUpdateEqual(remote, props));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRemotePropertiesReturnsNullBecauseNoUpdate()
|
public void testGetRemoteUpdateReturnsNullBecauseNoUpdate()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
@@ -543,13 +776,12 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
assertNull(t.getRemoteUpdate(txn, contact.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRemotePropertiesReturnsNullBecauseEmptyUpdate()
|
public void testGetRemoteUpdateNoMailbox() throws Exception {
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
@@ -558,9 +790,10 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
BdfList body = BdfList.of(1, someClientSupports, emptyServerSupports,
|
||||||
|
emptyPropsDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
@@ -571,20 +804,20 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
emptyPropsDict);
|
someClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
will(returnValue(null));
|
will(returnValue(updateNoMailbox));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
MailboxUpdate remote = t.getRemoteUpdate(txn, contact.getId());
|
||||||
|
assertTrue(mailboxUpdateEqual(remote, updateNoMailbox));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetLocalProperties()
|
public void testGetLocalUpdate() throws Exception {
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
@@ -593,9 +826,10 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
|
||||||
|
propsDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
@@ -606,46 +840,20 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
propsDict);
|
someClientSupports, someServerSupports, propsDict);
|
||||||
will(returnValue(props));
|
will(returnValue(updateWithMailbox));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
MailboxPropertiesUpdate local =
|
MailboxUpdate local = t.getLocalUpdate(txn, contact.getId());
|
||||||
t.getLocalProperties(txn, contact.getId());
|
assertTrue(mailboxUpdateEqual(local, updateWithMailbox));
|
||||||
assertTrue(mailboxPropertiesUpdateEqual(local, props));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetLocalPropertiesReturnsNullBecauseNoUpdate()
|
public void testGetLocalUpdateNoMailbox() throws Exception {
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
|
||||||
Contact contact = getContact();
|
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
|
||||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
|
||||||
new LinkedHashMap<>();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
|
||||||
will(returnValue(contact));
|
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
|
||||||
MAJOR_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
|
||||||
contactGroup.getId());
|
|
||||||
will(returnValue(emptyMessageMetadata));
|
|
||||||
}});
|
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
|
||||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetLocalPropertiesReturnsNullBecauseEmptyUpdate()
|
|
||||||
throws Exception {
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact();
|
Contact contact = getContact();
|
||||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
@@ -654,9 +862,10 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
MessageId messageId = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
messageMetadata.put(messageId, metaDictionary);
|
||||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
BdfList body = BdfList.of(1, someClientSupports, emptyServerSupports,
|
||||||
|
emptyPropsDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
@@ -667,21 +876,24 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(body));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
emptyPropsDict);
|
someClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
will(returnValue(null));
|
will(returnValue(updateNoMailbox));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxPropertyManagerImpl t = createInstance();
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
MailboxUpdate local = t.getLocalUpdate(txn, contact.getId());
|
||||||
|
assertTrue(mailboxUpdateEqual(local, updateNoMailbox));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectStoreMessage(Transaction txn, GroupId g,
|
private void expectStoreMessage(Transaction txn, GroupId g,
|
||||||
BdfDictionary properties, long version, boolean local)
|
long version, BdfList clientSupports, BdfList serverSupports,
|
||||||
|
BdfDictionary properties, boolean local)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
BdfList body = BdfList.of(version, properties);
|
BdfList body = BdfList.of(version, clientSupports, serverSupports,
|
||||||
|
properties);
|
||||||
Message message = getMessage(g);
|
Message message = getMessage(g);
|
||||||
long timestamp = message.getTimestamp();
|
long timestamp = message.getTimestamp();
|
||||||
BdfDictionary meta = BdfDictionary.of(
|
BdfDictionary meta = BdfDictionary.of(
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
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.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class MailboxUpdateValidatorTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||||
|
|
||||||
|
private final BdfDictionary bdfDict;
|
||||||
|
private final BdfList emptyServerSupports;
|
||||||
|
private final BdfList someClientSupports;
|
||||||
|
private final BdfList someServerSupports;
|
||||||
|
private final MailboxUpdateWithMailbox updateMailbox;
|
||||||
|
private final MailboxUpdate updateNoMailbox;
|
||||||
|
private final Group group;
|
||||||
|
private final Message message;
|
||||||
|
private final MailboxUpdateValidator muv;
|
||||||
|
|
||||||
|
public MailboxUpdateValidatorTest() {
|
||||||
|
// Just dummies, clientHelper is mocked so our test is a bit shallow;
|
||||||
|
// not testing
|
||||||
|
// {@link ClientHelper#parseAndValidateMailboxUpdate(BdfList, BdfList, BdfDictionary)}
|
||||||
|
emptyServerSupports = new BdfList();
|
||||||
|
someClientSupports = BdfList.of(BdfList.of(1, 0));
|
||||||
|
List<MailboxVersion> someClientSupportsList =
|
||||||
|
singletonList(new MailboxVersion(1, 0));
|
||||||
|
someServerSupports = BdfList.of(BdfList.of(1, 0));
|
||||||
|
bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
||||||
|
|
||||||
|
MailboxProperties props = getMailboxProperties(false,
|
||||||
|
singletonList(new MailboxVersion(1, 0)));
|
||||||
|
updateMailbox = new MailboxUpdateWithMailbox(
|
||||||
|
singletonList(new MailboxVersion(1, 0)), props);
|
||||||
|
updateNoMailbox = new MailboxUpdate(someClientSupportsList);
|
||||||
|
|
||||||
|
|
||||||
|
group = getGroup(MailboxUpdateManager.CLIENT_ID,
|
||||||
|
MailboxUpdateManager.MAJOR_VERSION);
|
||||||
|
message = getMessage(group.getId());
|
||||||
|
|
||||||
|
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
muv = new MailboxUpdateValidator(clientHelper, metadataEncoder,
|
||||||
|
clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateMessageBody() throws IOException {
|
||||||
|
BdfList body =
|
||||||
|
BdfList.of(4, someClientSupports, someServerSupports, bdfDict);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
|
someClientSupports, someServerSupports, bdfDict);
|
||||||
|
will(returnValue(updateMailbox));
|
||||||
|
}});
|
||||||
|
|
||||||
|
BdfDictionary result =
|
||||||
|
muv.validateMessage(message, group, body).getDictionary();
|
||||||
|
assertEquals(4, result.getLong("version").longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testValidateWrongVersionValue() throws IOException {
|
||||||
|
BdfList body = BdfList.of(-1, bdfDict);
|
||||||
|
muv.validateMessage(message, group, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testValidateWrongVersionType() throws IOException {
|
||||||
|
BdfList body = BdfList.of(bdfDict, bdfDict);
|
||||||
|
muv.validateMessage(message, group, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyProperties() throws IOException {
|
||||||
|
BdfDictionary emptyBdfDict = new BdfDictionary();
|
||||||
|
BdfList body = BdfList.of(42, someClientSupports, emptyServerSupports,
|
||||||
|
emptyBdfDict);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
|
someClientSupports, emptyServerSupports, emptyBdfDict);
|
||||||
|
will(returnValue(updateNoMailbox));
|
||||||
|
}});
|
||||||
|
|
||||||
|
BdfDictionary result =
|
||||||
|
muv.validateMessage(message, group, body).getDictionary();
|
||||||
|
assertEquals(42, result.getLong("version").longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
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.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.lib.action.DoAllAction;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final MailboxApiCaller mailboxApiCaller =
|
||||||
|
context.mock(MailboxApiCaller.class);
|
||||||
|
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
|
||||||
|
private final TransactionManager db =
|
||||||
|
context.mock(TransactionManager.class);
|
||||||
|
private final MailboxSettingsManager mailboxSettingsManager =
|
||||||
|
context.mock(MailboxSettingsManager.class);
|
||||||
|
private final Cancellable task = context.mock(Cancellable.class);
|
||||||
|
private final ConnectivityObserver observer =
|
||||||
|
context.mock(ConnectivityObserver.class);
|
||||||
|
|
||||||
|
private final MailboxProperties properties =
|
||||||
|
getMailboxProperties(true, CLIENT_SUPPORTS);
|
||||||
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
|
||||||
|
OwnMailboxConnectivityChecker checker = createChecker();
|
||||||
|
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
|
||||||
|
returnValue(task)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer);
|
||||||
|
|
||||||
|
// When the check succeeds, the success should be recorded in the DB
|
||||||
|
// and the observer should be called
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(mailboxApi).checkStatus(properties);
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
|
||||||
|
oneOf(observer).onConnectivityCheckSucceeded();
|
||||||
|
}});
|
||||||
|
|
||||||
|
// The call should not be retried
|
||||||
|
assertFalse(apiCall.get().callApi());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObserverIsNotCalledWhenCheckFails() throws Exception {
|
||||||
|
OwnMailboxConnectivityChecker checker = createChecker();
|
||||||
|
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
// When checkConnectivity() is called a check should be started
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
|
||||||
|
will(new DoAllAction(
|
||||||
|
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
|
||||||
|
returnValue(task)
|
||||||
|
));
|
||||||
|
}});
|
||||||
|
|
||||||
|
checker.checkConnectivity(properties, observer);
|
||||||
|
|
||||||
|
// When the check fails, the failure should be recorded in the DB and
|
||||||
|
// the observer should not be called
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(mailboxApi).checkStatus(properties);
|
||||||
|
will(throwException(new IOException()));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(mailboxSettingsManager)
|
||||||
|
.recordFailedConnectionAttempt(txn, now);
|
||||||
|
}});
|
||||||
|
|
||||||
|
// The call should be retried
|
||||||
|
assertTrue(apiCall.get().callApi());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OwnMailboxConnectivityChecker createChecker() {
|
||||||
|
return new OwnMailboxConnectivityChecker(clock, mailboxApiCaller,
|
||||||
|
mailboxApi, db, mailboxSettingsManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin;
|
package org.briarproject.bramble.plugin;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
@@ -20,7 +21,6 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
|||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.rendezvous;
|
package org.briarproject.bramble.rendezvous;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
import org.briarproject.bramble.api.contact.PendingContact;
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
import org.briarproject.bramble.api.contact.PendingContactState;
|
import org.briarproject.bramble.api.contact.PendingContactState;
|
||||||
@@ -27,7 +28,6 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
|
|||||||
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
|
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
|
public class EagerSimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private static final int MAX_LATENCY = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
|
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||||
|
private final SyncRecordWriter recordWriter =
|
||||||
|
context.mock(SyncRecordWriter.class);
|
||||||
|
|
||||||
|
private final ContactId contactId = getContactId();
|
||||||
|
private final TransportId transportId = getTransportId();
|
||||||
|
private final Ack ack =
|
||||||
|
new Ack(singletonList(new MessageId(getRandomId())));
|
||||||
|
private final Message message = getMessage(new GroupId(getRandomId()),
|
||||||
|
MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
private final Message message1 = getMessage(new GroupId(getRandomId()),
|
||||||
|
MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNothingToSendEagerly() throws Exception {
|
||||||
|
EagerSimplexOutgoingSession session =
|
||||||
|
new EagerSimplexOutgoingSession(db, eventBus, contactId,
|
||||||
|
transportId, MAX_LATENCY, streamWriter, recordWriter);
|
||||||
|
|
||||||
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
|
Transaction noIdsTxn = new Transaction(null, true);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// Add listener
|
||||||
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
|
// No acks to send
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(noAckTxn));
|
||||||
|
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(null));
|
||||||
|
// No messages to send
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(noIdsTxn));
|
||||||
|
oneOf(db).getUnackedMessagesToSend(noIdsTxn, contactId);
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
// Send the end of stream marker
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
// Remove listener
|
||||||
|
oneOf(eventBus).removeListener(session);
|
||||||
|
}});
|
||||||
|
|
||||||
|
session.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSomethingToSendEagerly() throws Exception {
|
||||||
|
EagerSimplexOutgoingSession session =
|
||||||
|
new EagerSimplexOutgoingSession(db, eventBus, contactId,
|
||||||
|
transportId, MAX_LATENCY, streamWriter, recordWriter);
|
||||||
|
|
||||||
|
Transaction ackTxn = new Transaction(null, false);
|
||||||
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
|
Transaction idsTxn = new Transaction(null, true);
|
||||||
|
Transaction msgTxn = new Transaction(null, false);
|
||||||
|
Transaction msgTxn1 = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// Add listener
|
||||||
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
|
// One ack to send
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(ackTxn));
|
||||||
|
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(ack));
|
||||||
|
oneOf(recordWriter).writeAck(ack);
|
||||||
|
// No more acks
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(noAckTxn));
|
||||||
|
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(null));
|
||||||
|
// Two messages to send
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn));
|
||||||
|
oneOf(db).getUnackedMessagesToSend(idsTxn, contactId);
|
||||||
|
will(returnValue(asList(message.getId(), message1.getId())));
|
||||||
|
// Try to send the first message - it's no longer shared
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(msgTxn));
|
||||||
|
oneOf(db).getMessageToSend(msgTxn, contactId, message.getId(),
|
||||||
|
MAX_LATENCY, true);
|
||||||
|
will(returnValue(null));
|
||||||
|
// Send the second message
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(msgTxn1));
|
||||||
|
oneOf(db).getMessageToSend(msgTxn1, contactId, message1.getId(),
|
||||||
|
MAX_LATENCY, true);
|
||||||
|
will(returnValue(message1));
|
||||||
|
oneOf(recordWriter).writeMessage(message1);
|
||||||
|
// Send the end of stream marker
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
// Remove listener
|
||||||
|
oneOf(eventBus).removeListener(session);
|
||||||
|
}});
|
||||||
|
|
||||||
|
session.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
|
import org.briarproject.bramble.api.sync.DeferredSendHandler;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
|
public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private static final int MAX_LATENCY = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
|
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||||
|
private final SyncRecordWriter recordWriter =
|
||||||
|
context.mock(SyncRecordWriter.class);
|
||||||
|
private final DeferredSendHandler deferredSendHandler =
|
||||||
|
context.mock(DeferredSendHandler.class);
|
||||||
|
|
||||||
|
private final ContactId contactId = getContactId();
|
||||||
|
private final TransportId transportId = getTransportId();
|
||||||
|
private final Message message = getMessage(new GroupId(getRandomId()),
|
||||||
|
MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
private final Message message1 = getMessage(new GroupId(getRandomId()),
|
||||||
|
MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
private final int versionRecordBytes = RECORD_HEADER_BYTES + 1;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNothingToSend() throws Exception {
|
||||||
|
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||||
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
|
streamWriter, recordWriter, deferredSendHandler,
|
||||||
|
MAX_FILE_PAYLOAD_BYTES);
|
||||||
|
|
||||||
|
Transaction noAckIdTxn = new Transaction(null, true);
|
||||||
|
Transaction noMsgIdTxn = new Transaction(null, true);
|
||||||
|
|
||||||
|
long capacityForMessages = MAX_FILE_PAYLOAD_BYTES - versionRecordBytes;
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// Add listener
|
||||||
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
|
// Calculate capacity for acks
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes));
|
||||||
|
// No messages to ack
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(noAckIdTxn));
|
||||||
|
oneOf(db).getMessagesToAck(noAckIdTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
// Calculate capacity for messages
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes));
|
||||||
|
// No messages to send
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(noMsgIdTxn));
|
||||||
|
oneOf(db).getMessagesToSend(noMsgIdTxn, contactId,
|
||||||
|
capacityForMessages, MAX_LATENCY);
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
// Send the end of stream marker
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
// Remove listener
|
||||||
|
oneOf(eventBus).removeListener(session);
|
||||||
|
}});
|
||||||
|
|
||||||
|
session.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSomethingToSend() throws Exception {
|
||||||
|
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||||
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
|
streamWriter, recordWriter, deferredSendHandler,
|
||||||
|
MAX_FILE_PAYLOAD_BYTES);
|
||||||
|
|
||||||
|
Transaction ackIdTxn = new Transaction(null, true);
|
||||||
|
Transaction noAckIdTxn = new Transaction(null, true);
|
||||||
|
Transaction msgIdTxn = new Transaction(null, true);
|
||||||
|
Transaction msgTxn = new Transaction(null, true);
|
||||||
|
|
||||||
|
int ackRecordBytes = RECORD_HEADER_BYTES + MessageId.LENGTH;
|
||||||
|
long capacityForMessages =
|
||||||
|
MAX_FILE_PAYLOAD_BYTES - versionRecordBytes - ackRecordBytes;
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// Add listener
|
||||||
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
|
// Calculate capacity for acks
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes));
|
||||||
|
// One message to ack
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(ackIdTxn));
|
||||||
|
oneOf(db).getMessagesToAck(ackIdTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(singletonList(message.getId())));
|
||||||
|
// Send the ack
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes));
|
||||||
|
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||||
|
oneOf(deferredSendHandler)
|
||||||
|
.onAckSent(singletonList(message.getId()));
|
||||||
|
// No more messages to ack
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(noAckIdTxn));
|
||||||
|
oneOf(db).getMessagesToAck(noAckIdTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
// Calculate capacity for messages
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes + ackRecordBytes));
|
||||||
|
// One message to send
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(msgIdTxn));
|
||||||
|
oneOf(db).getMessagesToSend(msgIdTxn, contactId,
|
||||||
|
capacityForMessages, MAX_LATENCY);
|
||||||
|
will(returnValue(singletonList(message1.getId())));
|
||||||
|
// Send the message
|
||||||
|
oneOf(db).transactionWithNullableResult(with(true),
|
||||||
|
withNullableDbCallable(msgTxn));
|
||||||
|
oneOf(db).getMessageToSend(msgTxn, contactId, message1.getId(),
|
||||||
|
MAX_LATENCY, false);
|
||||||
|
will(returnValue(message1));
|
||||||
|
oneOf(recordWriter).writeMessage(message1);
|
||||||
|
oneOf(deferredSendHandler).onMessageSent(message1.getId());
|
||||||
|
// Send the end of stream marker
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
// Remove listener
|
||||||
|
oneOf(eventBus).removeListener(session);
|
||||||
|
}});
|
||||||
|
|
||||||
|
session.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllCapacityUsedByAcks() throws Exception {
|
||||||
|
// The file has enough capacity for a max-size ack record, another
|
||||||
|
// ack record with one message ID, and a few bytes left over
|
||||||
|
long capacity = RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS
|
||||||
|
+ RECORD_HEADER_BYTES + MessageId.LENGTH + MessageId.LENGTH - 1;
|
||||||
|
|
||||||
|
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
|
||||||
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
|
streamWriter, recordWriter, deferredSendHandler, capacity);
|
||||||
|
|
||||||
|
Transaction ackIdTxn1 = new Transaction(null, true);
|
||||||
|
Transaction ackIdTxn2 = new Transaction(null, true);
|
||||||
|
|
||||||
|
int firstAckRecordBytes =
|
||||||
|
RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS;
|
||||||
|
int secondAckRecordBytes = RECORD_HEADER_BYTES + MessageId.LENGTH;
|
||||||
|
|
||||||
|
List<MessageId> idsInFirstAck = new ArrayList<>(MAX_MESSAGE_IDS);
|
||||||
|
for (int i = 0; i < MAX_MESSAGE_IDS; i++) {
|
||||||
|
idsInFirstAck.add(new MessageId(getRandomId()));
|
||||||
|
}
|
||||||
|
List<MessageId> idsInSecondAck =
|
||||||
|
singletonList(new MessageId(getRandomId()));
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
// Add listener
|
||||||
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
|
// Calculate capacity for acks
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes));
|
||||||
|
// Load the IDs for the first ack record
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(ackIdTxn1));
|
||||||
|
oneOf(db).getMessagesToAck(ackIdTxn1, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(idsInFirstAck));
|
||||||
|
// Send the first ack record
|
||||||
|
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||||
|
oneOf(deferredSendHandler).onAckSent(idsInFirstAck);
|
||||||
|
// Calculate remaining capacity for acks
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes + firstAckRecordBytes));
|
||||||
|
// Load the IDs for the second ack record
|
||||||
|
oneOf(db).transactionWithResult(with(true),
|
||||||
|
withDbCallable(ackIdTxn2));
|
||||||
|
oneOf(db).getMessagesToAck(ackIdTxn2, contactId, 1);
|
||||||
|
will(returnValue(idsInSecondAck));
|
||||||
|
// Send the second ack record
|
||||||
|
oneOf(recordWriter).writeAck(with(any(Ack.class)));
|
||||||
|
oneOf(deferredSendHandler).onAckSent(idsInSecondAck);
|
||||||
|
// Not enough capacity left for another ack
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes + firstAckRecordBytes
|
||||||
|
+ secondAckRecordBytes));
|
||||||
|
// Not enough capacity left for any messages
|
||||||
|
oneOf(recordWriter).getBytesWritten();
|
||||||
|
will(returnValue((long) versionRecordBytes + firstAckRecordBytes
|
||||||
|
+ secondAckRecordBytes));
|
||||||
|
// Send the end of stream marker
|
||||||
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
|
// Remove listener
|
||||||
|
oneOf(eventBus).removeListener(session);
|
||||||
|
}});
|
||||||
|
|
||||||
|
session.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,18 +14,12 @@ import org.briarproject.bramble.api.sync.Versions;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.sync.SimplexOutgoingSession.BATCH_CAPACITY;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
@@ -41,21 +35,18 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
private final SyncRecordWriter recordWriter =
|
private final SyncRecordWriter recordWriter =
|
||||||
context.mock(SyncRecordWriter.class);
|
context.mock(SyncRecordWriter.class);
|
||||||
|
|
||||||
private final Executor dbExecutor = new ImmediateExecutor();
|
|
||||||
private final ContactId contactId = getContactId();
|
private final ContactId contactId = getContactId();
|
||||||
private final TransportId transportId = getTransportId();
|
private final TransportId transportId = getTransportId();
|
||||||
private final Ack ack =
|
private final Ack ack =
|
||||||
new Ack(singletonList(new MessageId(getRandomId())));
|
new Ack(singletonList(new MessageId(getRandomId())));
|
||||||
private final Message message = getMessage(new GroupId(getRandomId()),
|
private final Message message = getMessage(new GroupId(getRandomId()),
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
MAX_MESSAGE_BODY_LENGTH);
|
||||||
private final Message message1 = getMessage(new GroupId(getRandomId()),
|
|
||||||
MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNothingToSend() throws Exception {
|
public void testNothingToSend() throws Exception {
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
Transaction noMsgTxn = new Transaction(null, false);
|
Transaction noMsgTxn = new Transaction(null, false);
|
||||||
@@ -74,7 +65,7 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(noMsgTxn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
BATCH_CAPACITY, MAX_LATENCY);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -85,44 +76,11 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNothingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
true, streamWriter, recordWriter);
|
|
||||||
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
|
||||||
Transaction noIdsTxn = new Transaction(null, true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// Add listener
|
|
||||||
oneOf(eventBus).addListener(session);
|
|
||||||
// Send the protocol versions
|
|
||||||
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
|
||||||
// No acks to send
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// No messages to send
|
|
||||||
oneOf(db).transactionWithResult(with(true),
|
|
||||||
withDbCallable(noIdsTxn));
|
|
||||||
oneOf(db).getUnackedMessagesToSend(noIdsTxn, contactId);
|
|
||||||
will(returnValue(emptyMap()));
|
|
||||||
// Send the end of stream marker
|
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
|
||||||
// Remove listener
|
|
||||||
oneOf(eventBus).removeListener(session);
|
|
||||||
}});
|
|
||||||
|
|
||||||
session.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSomethingToSend() throws Exception {
|
public void testSomethingToSend() throws Exception {
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
false, streamWriter, recordWriter);
|
streamWriter, recordWriter);
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
Transaction ackTxn = new Transaction(null, false);
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
Transaction noAckTxn = new Transaction(null, false);
|
||||||
@@ -140,23 +98,23 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
will(returnValue(ack));
|
will(returnValue(ack));
|
||||||
oneOf(recordWriter).writeAck(ack);
|
oneOf(recordWriter).writeAck(ack);
|
||||||
|
// No more acks
|
||||||
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
|
withNullableDbCallable(noAckTxn));
|
||||||
|
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
||||||
|
will(returnValue(null));
|
||||||
// One message to send
|
// One message to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(msgTxn));
|
withNullableDbCallable(msgTxn));
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
oneOf(db).generateBatch(msgTxn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
BATCH_CAPACITY, MAX_LATENCY);
|
||||||
will(returnValue(singletonList(message)));
|
will(returnValue(singletonList(message)));
|
||||||
oneOf(recordWriter).writeMessage(message);
|
oneOf(recordWriter).writeMessage(message);
|
||||||
// No more acks
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// No more messages
|
// No more messages
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noMsgTxn));
|
withNullableDbCallable(noMsgTxn));
|
||||||
oneOf(db).generateBatch(noMsgTxn, contactId,
|
oneOf(db).generateBatch(noMsgTxn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_BYTES, MAX_LATENCY);
|
BATCH_CAPACITY, MAX_LATENCY);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
// Send the end of stream marker
|
// Send the end of stream marker
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
oneOf(streamWriter).sendEndOfStream();
|
||||||
@@ -166,63 +124,4 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
session.run();
|
session.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSomethingToSendEagerly() throws Exception {
|
|
||||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
|
||||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
|
||||||
true, streamWriter, recordWriter);
|
|
||||||
|
|
||||||
Map<MessageId, Integer> unacked = new LinkedHashMap<>();
|
|
||||||
unacked.put(message.getId(), message.getRawLength());
|
|
||||||
unacked.put(message1.getId(), message1.getRawLength());
|
|
||||||
|
|
||||||
Transaction ackTxn = new Transaction(null, false);
|
|
||||||
Transaction noAckTxn = new Transaction(null, false);
|
|
||||||
Transaction idsTxn = new Transaction(null, true);
|
|
||||||
Transaction msgTxn = new Transaction(null, false);
|
|
||||||
Transaction msgTxn1 = new Transaction(null, false);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// Add listener
|
|
||||||
oneOf(eventBus).addListener(session);
|
|
||||||
// Send the protocol versions
|
|
||||||
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
|
||||||
// One ack to send
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(ackTxn));
|
|
||||||
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(ack));
|
|
||||||
oneOf(recordWriter).writeAck(ack);
|
|
||||||
// No more acks
|
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
|
||||||
withNullableDbCallable(noAckTxn));
|
|
||||||
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
|
|
||||||
will(returnValue(null));
|
|
||||||
// Two messages to send
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(idsTxn));
|
|
||||||
oneOf(db).getUnackedMessagesToSend(idsTxn, contactId);
|
|
||||||
will(returnValue(unacked));
|
|
||||||
// Send the first message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn));
|
|
||||||
oneOf(db).generateBatch(msgTxn, contactId,
|
|
||||||
singletonList(message.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message)));
|
|
||||||
oneOf(recordWriter).writeMessage(message);
|
|
||||||
// Send the second message
|
|
||||||
oneOf(db).transactionWithResult(with(false),
|
|
||||||
withDbCallable(msgTxn1));
|
|
||||||
oneOf(db).generateBatch(msgTxn1, contactId,
|
|
||||||
singletonList(message1.getId()), MAX_LATENCY);
|
|
||||||
will(returnValue(singletonList(message1)));
|
|
||||||
oneOf(recordWriter).writeMessage(message1);
|
|
||||||
// Send the end of stream marker
|
|
||||||
oneOf(streamWriter).sendEndOfStream();
|
|
||||||
// Remove listener
|
|
||||||
oneOf(eventBus).removeListener(session);
|
|
||||||
}});
|
|
||||||
|
|
||||||
session.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.system;
|
package org.briarproject.bramble.system;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
|||||||
@@ -345,11 +345,17 @@ public class TransportKeyAgreementIntegrationTest
|
|||||||
.canSendOutgoingStreams(aliceId, DUPLEX_TRANSPORT_ID));
|
.canSendOutgoingStreams(aliceId, DUPLEX_TRANSPORT_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync initial client versioning updates
|
// Sync client versioning update from Alice to Bob
|
||||||
syncMessage(alice, bob, bobId, 1, true);
|
syncMessage(alice, bob, bobId, 1, true);
|
||||||
|
// Sync client versioning update and ack from Bob to Alice
|
||||||
syncMessage(bob, alice, aliceId, 1, true);
|
syncMessage(bob, alice, aliceId, 1, true);
|
||||||
syncMessage(alice, bob, bobId, 1, true);
|
// Sync second client versioning update, mailbox properties and ack
|
||||||
sendAcks(bob, alice, aliceId, 1);
|
// from Alice to Bob
|
||||||
|
syncMessage(alice, bob, bobId, 2, true);
|
||||||
|
// Sync mailbox properties and ack from Bob to Alice
|
||||||
|
syncMessage(bob, alice, aliceId, 1, true);
|
||||||
|
// Sync final ack from Alice to Bob
|
||||||
|
sendAcks(alice, bob, bobId, 1);
|
||||||
|
|
||||||
return new Pair<>(aliceId, bobId);
|
return new Pair<>(aliceId, bobId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ dependencyVerification {
|
|||||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
|
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
|
||||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
'org.briarproject:jtorctl:0.4:jtorctl-0.4.jar:4e61f59dc9f3984438a7151c4df8d7c1f83d5fb3eb8c151acfc794a8fef85a36',
|
||||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||||
|
|||||||
@@ -9,13 +9,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
|
||||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
@@ -23,43 +19,18 @@ import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class UnixTorPluginFactory implements DuplexPluginFactory {
|
public class UnixTorPluginFactory extends TorPluginFactory {
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
getLogger(UnixTorPluginFactory.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 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 Clock clock;
|
|
||||||
private final File torDirectory;
|
|
||||||
private int torSocksPort;
|
|
||||||
private int torControlPort;
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@@ -73,74 +44,39 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
|||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
BatteryManager batteryManager,
|
BatteryManager batteryManager,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
|
CryptoComponent crypto,
|
||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort,
|
@TorControlPort int torControlPort) {
|
||||||
CryptoComponent crypto) {
|
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||||
this.ioExecutor = ioExecutor;
|
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
circumventionProvider, batteryManager, clock, crypto,
|
||||||
this.networkManager = networkManager;
|
torDirectory, torSocksPort, torControlPort);
|
||||||
this.locationUtils = locationUtils;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.torSocketFactory = torSocketFactory;
|
|
||||||
this.backoffFactory = backoffFactory;
|
|
||||||
this.resourceProvider = resourceProvider;
|
|
||||||
this.circumventionProvider = circumventionProvider;
|
|
||||||
this.batteryManager = batteryManager;
|
|
||||||
this.clock = clock;
|
|
||||||
this.torDirectory = torDirectory;
|
|
||||||
this.torSocksPort = torSocksPort;
|
|
||||||
this.torControlPort = torControlPort;
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public TransportId getId() {
|
String getArchitectureForTorBinary() {
|
||||||
return TorConstants.ID;
|
if (!isLinux()) return null;
|
||||||
}
|
String arch = System.getProperty("os.arch");
|
||||||
|
|
||||||
@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;
|
|
||||||
if (isLinux()) {
|
|
||||||
String arch = System.getProperty("os.arch");
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("System's os.arch is " + arch);
|
|
||||||
}
|
|
||||||
if (arch.equals("amd64")) {
|
|
||||||
architecture = "linux-x86_64";
|
|
||||||
} else if (arch.equals("aarch64")) {
|
|
||||||
architecture = "linux-aarch64";
|
|
||||||
} else if (arch.equals("arm")) {
|
|
||||||
architecture = "linux-armhf";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (architecture == null) {
|
|
||||||
LOG.info("Tor is not supported on this architecture");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("The selected architecture for Tor is " + architecture);
|
LOG.info("System's os.arch is " + arch);
|
||||||
}
|
}
|
||||||
|
if (arch.equals("amd64")) return "linux-x86_64";
|
||||||
|
else if (arch.equals("aarch64")) return "linux-aarch64";
|
||||||
|
else if (arch.equals("arm")) return "linux-armhf";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
@Override
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
TorPlugin createPluginInstance(Backoff backoff,
|
||||||
TorRendezvousCrypto torRendezvousCrypto =
|
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||||
new TorRendezvousCryptoImpl(crypto);
|
String architecture) {
|
||||||
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||||
networkManager, locationUtils, torSocketFactory, clock,
|
networkManager, locationUtils, torSocketFactory, clock,
|
||||||
resourceProvider, circumventionProvider, batteryManager,
|
resourceProvider, circumventionProvider, batteryManager,
|
||||||
backoff, torRendezvousCrypto, callback, architecture,
|
backoff, torRendezvousCrypto, callback, architecture,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||||
torControlPort);
|
torControlPort);
|
||||||
eventBus.addListener(plugin);
|
|
||||||
return plugin;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
|
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
|
||||||
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
|
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
|
||||||
private final static int ATTEMPTS_PER_BRIDGE = 5;
|
private final static int ATTEMPTS_PER_BRIDGE = 5;
|
||||||
|
// Use different ports from Briar Desktop to avoid conflicts
|
||||||
|
private final static int SOCKS_PORT = DEFAULT_SOCKS_PORT + 10;
|
||||||
|
private final static int CONTROL_PORT = DEFAULT_CONTROL_PORT + 10;
|
||||||
|
|
||||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||||
|
|
||||||
@@ -164,8 +167,8 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
||||||
networkManager, locationUtils, eventBus, torSocketFactory,
|
networkManager, locationUtils, eventBus, torSocketFactory,
|
||||||
backoffFactory, resourceProvider, bridgeProvider,
|
backoffFactory, resourceProvider, bridgeProvider,
|
||||||
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,
|
batteryManager, clock, crypto, torDir,
|
||||||
DEFAULT_CONTROL_PORT, crypto);
|
SOCKS_PORT, CONTROL_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10406
|
versionCode 10408
|
||||||
versionName "1.4.6"
|
versionName "1.4.8"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -100,6 +100,7 @@ dependencies {
|
|||||||
implementation project(path: ':briar-core', configuration: 'default')
|
implementation project(path: ':briar-core', configuration: 'default')
|
||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
implementation project(':bramble-android')
|
implementation project(':bramble-android')
|
||||||
|
implementation 'org.briarproject:dont-kill-me-lib:0.2.2'
|
||||||
|
|
||||||
implementation 'androidx.fragment:fragment:1.3.4'
|
implementation 'androidx.fragment:fragment:1.3.4'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
@@ -114,7 +115,7 @@ dependencies {
|
|||||||
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
||||||
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
|
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
|
||||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.0'
|
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.0'
|
||||||
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
|
implementation 'com.vanniktech:emoji-google:0.7.0' // newer versions need minSdk 21
|
||||||
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
|
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
def glideVersion = '4.11.0'
|
def glideVersion = '4.11.0'
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user