mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
232 Commits
alpha-1.4.
...
beta-1.4.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
592daf9c20 | ||
|
|
3922270db1 | ||
|
|
4ba4e41e69 | ||
|
|
1f699238a9 | ||
|
|
b8e91a12e8 | ||
|
|
06eb01ab0a | ||
|
|
d82509f3ce | ||
|
|
b01c306500 | ||
|
|
61e7635b9f | ||
|
|
f2f356cbd4 | ||
|
|
28f3ab1310 | ||
|
|
8bb3a83ccb | ||
|
|
a742b007ef | ||
|
|
6bfd7bcc4f | ||
|
|
17f5fc7518 | ||
|
|
8dcf988399 | ||
|
|
05bf3833cf | ||
|
|
c39c2ce124 | ||
|
|
0b93af5d71 | ||
|
|
f8e3579a92 | ||
|
|
54e434d812 | ||
|
|
13c3974f73 | ||
|
|
aeb2a370e1 | ||
|
|
0aff23a067 | ||
|
|
a2a2da0260 | ||
|
|
4d7a3bca62 | ||
|
|
91d5698fe9 | ||
|
|
7266c6ee6b | ||
|
|
06b539b911 | ||
|
|
486ba4a3fc | ||
|
|
7f987667fe | ||
|
|
8d22a0ffaf | ||
|
|
43d28608f5 | ||
|
|
c84d3f7707 | ||
|
|
2843e15905 | ||
|
|
a2fb388aa6 | ||
|
|
b7b253cf24 | ||
|
|
f05e9dd746 | ||
|
|
e2a63ee361 | ||
|
|
ff9f706670 | ||
|
|
10ab60569b | ||
|
|
d77d1d67aa | ||
|
|
924425522a | ||
|
|
356e0ee07b | ||
|
|
61658655ff | ||
|
|
40086ffde2 | ||
|
|
1551142e98 | ||
|
|
1c6fb6491a | ||
|
|
cfd4e85e77 | ||
|
|
4d6abfabf7 | ||
|
|
a38933df66 | ||
|
|
4993873ae2 | ||
|
|
02b805ce42 | ||
|
|
1a6ba16a59 | ||
|
|
654a05df8a | ||
|
|
ffe1876337 | ||
|
|
98963955b1 | ||
|
|
d83efce002 | ||
|
|
efb1b8c1ad | ||
|
|
3f36db8b3a | ||
|
|
a2f4e70a48 | ||
|
|
01e72eff40 | ||
|
|
dbcea3e1d1 | ||
|
|
6288577daa | ||
|
|
5d363496bd | ||
|
|
75b5c92495 | ||
|
|
bcc98cc4c9 | ||
|
|
2d605089bc | ||
|
|
01f8be1b66 | ||
|
|
eac6d0aa40 | ||
|
|
713be403eb | ||
|
|
2fd948b81d | ||
|
|
62af5e858c | ||
|
|
2201585a34 | ||
|
|
97d11cc602 | ||
|
|
79f41064e4 | ||
|
|
9aacd9d3d8 | ||
|
|
78f4dee43d | ||
|
|
2b4a1cf54b | ||
|
|
bb71de1a78 | ||
|
|
08bf13e44f | ||
|
|
cc7de2c70a | ||
|
|
0f4aa8027a | ||
|
|
b161a5e115 | ||
|
|
e112f69c4e | ||
|
|
4623d03c93 | ||
|
|
b128220be3 | ||
|
|
6aa24af94c | ||
|
|
de63a50662 | ||
|
|
5517ac14ed | ||
|
|
2672d82a40 | ||
|
|
63c0210047 | ||
|
|
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 | ||
|
|
74a3f54d28 | ||
|
|
edcb234b93 | ||
|
|
dae00c7e4e | ||
|
|
29b16c4d74 | ||
|
|
edd270abf3 | ||
|
|
47d412dd0a | ||
|
|
5d952ff68e | ||
|
|
9304a6b266 | ||
|
|
a99ec5ed51 | ||
|
|
40d58a9359 | ||
|
|
60a1a4d2d1 | ||
|
|
238aeb3abd | ||
|
|
62c16fad09 | ||
|
|
68e57bda0d | ||
|
|
0df73dbf0a | ||
|
|
5b648cbd35 | ||
|
|
5e7891d78a | ||
|
|
47085722da |
@@ -118,10 +118,3 @@ mailbox integration test:
|
|||||||
- cd "$CI_PROJECT_DIR"
|
- cd "$CI_PROJECT_DIR"
|
||||||
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
||||||
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
||||||
|
|
||||||
pre_release_tests:
|
|
||||||
extends: .optional_tests
|
|
||||||
script:
|
|
||||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10406
|
versionCode 10409
|
||||||
versionName "1.4.6"
|
versionName "1.4.9"
|
||||||
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
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ConnectionManager {
|
public interface ConnectionManager {
|
||||||
@@ -16,6 +17,17 @@ public interface ConnectionManager {
|
|||||||
*/
|
*/
|
||||||
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
|
void manageIncomingConnection(TransportId t, TransportConnectionReader r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages an incoming connection from a contact via a mailbox.
|
||||||
|
* <p>
|
||||||
|
* This method does not mark the tag as recognised until after the data
|
||||||
|
* has been read from the {@link TransportConnectionReader}, at which
|
||||||
|
* point the {@link TagController} is called to decide whether the tag
|
||||||
|
* should be marked as recognised.
|
||||||
|
*/
|
||||||
|
void manageIncomingConnection(TransportId t, TransportConnectionReader r,
|
||||||
|
TagController c);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages an incoming connection from a contact over a duplex transport.
|
* Manages an incoming connection from a contact over a duplex transport.
|
||||||
*/
|
*/
|
||||||
@@ -34,6 +46,14 @@ public interface ConnectionManager {
|
|||||||
void manageOutgoingConnection(ContactId c, TransportId t,
|
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||||
TransportConnectionWriter w);
|
TransportConnectionWriter w);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages an outgoing connection to a contact via a mailbox. The IDs of
|
||||||
|
* any messages sent or acked are added to the given
|
||||||
|
* {@link OutgoingSessionRecord}.
|
||||||
|
*/
|
||||||
|
void manageOutgoingConnection(ContactId c, TransportId t,
|
||||||
|
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages an outgoing connection to a contact over a duplex transport.
|
* Manages an outgoing connection to a contact over a duplex transport.
|
||||||
*/
|
*/
|
||||||
@@ -46,4 +66,21 @@ public interface ConnectionManager {
|
|||||||
*/
|
*/
|
||||||
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
void manageOutgoingConnection(PendingContactId p, TransportId t,
|
||||||
DuplexTransportConnection d);
|
DuplexTransportConnection d);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for controlling whether a tag should be marked as
|
||||||
|
* recognised.
|
||||||
|
*/
|
||||||
|
interface TagController {
|
||||||
|
/**
|
||||||
|
* This method is only called if a tag was read from the corresponding
|
||||||
|
* {@link TransportConnectionReader} and recognised.
|
||||||
|
*
|
||||||
|
* @param exception True if an exception was thrown while reading from
|
||||||
|
* the {@link TransportConnectionReader}, after successfully reading
|
||||||
|
* and recognising the tag.
|
||||||
|
* @return True if the tag should be marked as recognised.
|
||||||
|
*/
|
||||||
|
boolean shouldMarkTagAsRecognised(boolean exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,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 {
|
||||||
|
|
||||||
@@ -119,16 +126,11 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
TransportKeys k) throws DbException;
|
TransportKeys k) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there are any acks or messages to send to the given
|
* Returns true if there are any acks to send to the given contact.
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
*/
|
||||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
|
||||||
long maxLatency, boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
@@ -154,6 +156,18 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
|
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are any messages to send to the given contact
|
||||||
|
* over a transport with the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*
|
||||||
|
* @param eager True if messages that are not yet due for retransmission
|
||||||
|
* should be included
|
||||||
|
*/
|
||||||
|
boolean containsMessagesToSend(Transaction txn, ContactId c,
|
||||||
|
long maxLatency, boolean eager) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given pending contact.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -193,26 +207,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 +235,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 +348,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 +488,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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -498,15 +541,18 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
* message is due to be sent to the given contact. The returned value may
|
* message is due to be sent to the given contact over a transport with
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
* the given latency.
|
||||||
* no messages are scheduled to be sent.
|
* <p>
|
||||||
|
* The returned value may be zero if a message is due to be sent
|
||||||
|
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* Returns the pending contact with the given ID.
|
||||||
@@ -648,6 +694,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 +747,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,73 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
|
import static java.util.concurrent.TimeUnit.HOURS;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
|
||||||
|
public interface MailboxConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transport ID of the mailbox plugin.
|
||||||
|
*/
|
||||||
|
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mailbox API versions that we support as a client. This is reported to our
|
||||||
|
* contacts by {@link MailboxUpdateManager}.
|
||||||
|
*/
|
||||||
|
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
|
||||||
|
new MailboxVersion(1, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constant returned by
|
||||||
|
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||||
|
* when the server is too old to support our major version.
|
||||||
|
*/
|
||||||
|
int API_SERVER_TOO_OLD = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constant returned by
|
||||||
|
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}
|
||||||
|
* when we as a client are too old to support the server's major version.
|
||||||
|
*/
|
||||||
|
int API_CLIENT_TOO_OLD = -2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of a file that can be uploaded to or downloaded from
|
||||||
|
* a mailbox.
|
||||||
|
*/
|
||||||
|
int MAX_FILE_BYTES = 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of the plaintext payload of a file, such that the
|
||||||
|
* ciphertext is no more than {@link #MAX_FILE_BYTES}.
|
||||||
|
*/
|
||||||
|
int MAX_FILE_PAYLOAD_BYTES =
|
||||||
|
(MAX_FILE_BYTES - TAG_LENGTH - STREAM_HEADER_LENGTH)
|
||||||
|
/ MAX_FRAME_LENGTH * MAX_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of connection failures
|
||||||
|
* that indicate a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
int PROBLEM_NUM_CONNECTION_FAILURES = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time in milliseconds since the last connection success
|
||||||
|
* that need to pass to indicates a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum latency of the mailbox transport in milliseconds.
|
||||||
|
*/
|
||||||
|
long MAX_LATENCY = DAYS.toMillis(14);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for injecting the {@link File directory} where the Mailbox plugin
|
||||||
|
* should store its state.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Target({FIELD, METHOD, PARAMETER})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface MailboxDirectory {
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||||
|
|
||||||
|
class MailboxHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the highest major version that both client and server support
|
||||||
|
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
|
||||||
|
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
|
||||||
|
*/
|
||||||
|
static int getHighestCommonMajorVersion(
|
||||||
|
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||||
|
TreeSet<Integer> clientVersions = new TreeSet<>();
|
||||||
|
for (MailboxVersion version : client) {
|
||||||
|
clientVersions.add(version.getMajor());
|
||||||
|
}
|
||||||
|
TreeSet<Integer> serverVersions = new TreeSet<>();
|
||||||
|
for (MailboxVersion version : server) {
|
||||||
|
serverVersions.add(version.getMajor());
|
||||||
|
}
|
||||||
|
for (int clientVersion : clientVersions.descendingSet()) {
|
||||||
|
if (serverVersions.contains(clientVersion)) return clientVersion;
|
||||||
|
}
|
||||||
|
if (clientVersions.last() < serverVersions.last()) {
|
||||||
|
return API_CLIENT_TOO_OLD;
|
||||||
|
}
|
||||||
|
return API_SERVER_TOO_OLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -32,4 +34,23 @@ public interface MailboxManager {
|
|||||||
*/
|
*/
|
||||||
MailboxPairingTask startPairingTask(String qrCodePayload);
|
MailboxPairingTask startPairingTask(String qrCodePayload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used by the UI to test the mailbox connection.
|
||||||
|
*
|
||||||
|
* @return true (success) or false (error).
|
||||||
|
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
|
||||||
|
* {@link MailboxStatus}.
|
||||||
|
*/
|
||||||
|
boolean checkConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpairs the owner's mailbox and tries to wipe it.
|
||||||
|
* As this makes a network call, it should be run on the {@link IoExecutor}.
|
||||||
|
*
|
||||||
|
* @return true if we could wipe the mailbox, false if we couldn't.
|
||||||
|
* It is advised to inform the user to wipe the mailbox themselves,
|
||||||
|
* if we failed to wipe it.
|
||||||
|
*/
|
||||||
|
@IoExecutor
|
||||||
|
boolean unPair() throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.mailbox;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface MailboxPropertyManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unique ID of the mailbox property client.
|
|
||||||
*/
|
|
||||||
ClientId CLIENT_ID =
|
|
||||||
new ClientId("org.briarproject.bramble.mailbox.properties");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current major version of the mailbox property client.
|
|
||||||
*/
|
|
||||||
int MAJOR_VERSION = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current minor version of the mailbox property client.
|
|
||||||
*/
|
|
||||||
int MINOR_VERSION = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of properties required for a (non-empty) update message.
|
|
||||||
*/
|
|
||||||
int PROP_COUNT = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The required properties of a non-empty update message.
|
|
||||||
*/
|
|
||||||
String PROP_KEY_ONION = "onion";
|
|
||||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
|
||||||
String PROP_KEY_INBOXID = "inboxId";
|
|
||||||
String PROP_KEY_OUTBOXID = "outboxId";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Length of the Onion property.
|
|
||||||
*/
|
|
||||||
int PROP_ONION_LENGTH = 56;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message metadata key for the version number of a local or remote update,
|
|
||||||
* as a BDF long.
|
|
||||||
*/
|
|
||||||
String MSG_KEY_VERSION = "version";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message metadata key for whether an update is local or remote, as a BDF
|
|
||||||
* boolean.
|
|
||||||
*/
|
|
||||||
String MSG_KEY_LOCAL = "local";
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c)
|
|
||||||
throws DbException;
|
|
||||||
}
|
|
||||||
@@ -7,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,11 +28,16 @@ public interface MailboxSettingsManager {
|
|||||||
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
void removeOwnMailboxProperties(Transaction txn) throws DbException;
|
||||||
|
|
||||||
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||||
|
|
||||||
void recordSuccessfulConnection(Transaction txn, long now)
|
void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
void recordSuccessfulConnection(Transaction txn, long now,
|
||||||
|
List<MailboxVersion> versions) throws DbException;
|
||||||
|
|
||||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
@@ -47,7 +54,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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,20 +2,30 @@ package org.briarproject.bramble.api.mailbox;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class MailboxStatus {
|
public class MailboxStatus {
|
||||||
|
|
||||||
private final long lastAttempt, lastSuccess;
|
private final long lastAttempt, lastSuccess;
|
||||||
private final int attemptsSinceSuccess;
|
private final int attemptsSinceSuccess;
|
||||||
|
private final List<MailboxVersion> serverSupports;
|
||||||
|
|
||||||
public MailboxStatus(long lastAttempt, long lastSuccess,
|
public MailboxStatus(long lastAttempt, long lastSuccess,
|
||||||
int attemptsSinceSuccess) {
|
int attemptsSinceSuccess,
|
||||||
|
List<MailboxVersion> serverSupports) {
|
||||||
this.lastAttempt = lastAttempt;
|
this.lastAttempt = lastAttempt;
|
||||||
this.lastSuccess = lastSuccess;
|
this.lastSuccess = lastSuccess;
|
||||||
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
||||||
|
this.serverSupports = serverSupports;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,4 +66,21 @@ public class MailboxStatus {
|
|||||||
public int getAttemptsSinceSuccess() {
|
public int getAttemptsSinceSuccess() {
|
||||||
return attemptsSinceSuccess;
|
return attemptsSinceSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if this status indicates a problem with the mailbox.
|
||||||
|
*/
|
||||||
|
public boolean hasProblem(long now) {
|
||||||
|
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
|
||||||
|
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a positive integer if the mailbox is compatible. Same result as
|
||||||
|
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}.
|
||||||
|
*/
|
||||||
|
public int getMailboxCompatibility() {
|
||||||
|
return getHighestCommonMajorVersion(CLIENT_SUPPORTS, serverSupports);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface MailboxUpdateManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID of the mailbox update (properties) client.
|
||||||
|
*/
|
||||||
|
ClientId CLIENT_ID =
|
||||||
|
new ClientId("org.briarproject.bramble.mailbox.properties");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current major version of the mailbox update (properties) client.
|
||||||
|
*/
|
||||||
|
int MAJOR_VERSION = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current minor version of the mailbox update (properties) client.
|
||||||
|
*/
|
||||||
|
int MINOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of properties required for an update message with a mailbox.
|
||||||
|
*/
|
||||||
|
int PROP_COUNT = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The required properties of an update message with a mailbox.
|
||||||
|
*/
|
||||||
|
String PROP_KEY_ONION = "onion";
|
||||||
|
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||||
|
String PROP_KEY_INBOXID = "inboxId";
|
||||||
|
String PROP_KEY_OUTBOXID = "outboxId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the Onion property.
|
||||||
|
*/
|
||||||
|
int PROP_ONION_LENGTH = 56;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message metadata key for the version number of a local or remote update,
|
||||||
|
* as a BDF long.
|
||||||
|
*/
|
||||||
|
String MSG_KEY_VERSION = "version";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message metadata key for whether an update is local or remote, as a BDF
|
||||||
|
* boolean.
|
||||||
|
*/
|
||||||
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key in the client's local group for storing the clientSupports list that
|
||||||
|
* was last sent out.
|
||||||
|
*/
|
||||||
|
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest {@link MailboxUpdate} sent to the given contact.
|
||||||
|
* <p>
|
||||||
|
* If we have our own mailbox then the update will be a
|
||||||
|
* {@link MailboxUpdateWithMailbox} containing the
|
||||||
|
* {@link MailboxProperties} the contact should use for communicating with
|
||||||
|
* our mailbox.
|
||||||
|
*/
|
||||||
|
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest {@link MailboxUpdate} received from the given
|
||||||
|
* contact, or null if no update has been received.
|
||||||
|
* <p>
|
||||||
|
* If the contact has a mailbox then the update will be a
|
||||||
|
* {@link MailboxUpdateWithMailbox} containing the
|
||||||
|
* {@link MailboxProperties} we should use for communicating with the
|
||||||
|
* contact's mailbox.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
}
|
||||||
@@ -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,37 @@
|
|||||||
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for holding the IDs of messages sent and acked during an
|
||||||
|
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
|
||||||
|
* or acked at some later time.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
public class OutgoingSessionRecord {
|
||||||
|
|
||||||
|
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
|
||||||
|
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public void onAckSent(Collection<MessageId> acked) {
|
||||||
|
ackedIds.addAll(acked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessageSent(MessageId sent) {
|
||||||
|
sentIds.add(sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<MessageId> getAckedIds() {
|
||||||
|
return ackedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<MessageId> getSentIds() {
|
||||||
|
return sentIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,30 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface SyncSessionFactory {
|
public interface SyncSessionFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a session for receiving data from a contact.
|
||||||
|
*/
|
||||||
SyncSession createIncomingSession(ContactId c, InputStream in,
|
SyncSession createIncomingSession(ContactId c, InputStream in,
|
||||||
PriorityHandler handler);
|
PriorityHandler handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a session for sending data to a contact over a simplex transport.
|
||||||
|
*
|
||||||
|
* @param eager True if messages should be sent eagerly, ie regardless of
|
||||||
|
* whether they're due for retransmission.
|
||||||
|
*/
|
||||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, boolean eager, StreamWriter streamWriter);
|
long maxLatency, boolean eager, StreamWriter streamWriter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a session for sending data to a contact via a mailbox. The IDs
|
||||||
|
* of any messages sent or acked will be added to the given
|
||||||
|
* {@link OutgoingSessionRecord}.
|
||||||
|
*/
|
||||||
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
|
long maxLatency, StreamWriter streamWriter,
|
||||||
|
OutgoingSessionRecord sessionRecord);
|
||||||
|
|
||||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
@Nullable Priority priority);
|
@Nullable Priority priority);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.sync.event;
|
|||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -15,12 +16,19 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class GroupVisibilityUpdatedEvent extends Event {
|
public class GroupVisibilityUpdatedEvent extends Event {
|
||||||
|
|
||||||
|
private final Visibility visibility;
|
||||||
private final Collection<ContactId> affected;
|
private final Collection<ContactId> affected;
|
||||||
|
|
||||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
||||||
|
Collection<ContactId> affected) {
|
||||||
|
this.visibility = visibility;
|
||||||
this.affected = affected;
|
this.affected = affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Visibility getVisibility() {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contacts affected by the update.
|
* Returns the contacts affected by the update.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class IoUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void delete(File f) {
|
public static void delete(File f) {
|
||||||
if (!f.delete() && LOG.isLoggable(WARNING))
|
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class MailboxHelperTest {
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetHighestCommonMajorVersion() {
|
||||||
|
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2)));
|
||||||
|
assertEquals(2, getHighestCommonMajorVersion(v(1, 2), v(2, 3, 4)));
|
||||||
|
assertEquals(2, getHighestCommonMajorVersion(v(2, 3, 4), v(2)));
|
||||||
|
assertEquals(2, getHighestCommonMajorVersion(v(2), v(2, 3, 4)));
|
||||||
|
|
||||||
|
assertEquals(API_CLIENT_TOO_OLD,
|
||||||
|
getHighestCommonMajorVersion(v(2), v(3, 4)));
|
||||||
|
assertEquals(API_CLIENT_TOO_OLD,
|
||||||
|
getHighestCommonMajorVersion(v(2), v(1, 3)));
|
||||||
|
assertEquals(API_SERVER_TOO_OLD,
|
||||||
|
getHighestCommonMajorVersion(v(3, 4, 5), v(2)));
|
||||||
|
assertEquals(API_SERVER_TOO_OLD,
|
||||||
|
getHighestCommonMajorVersion(v(1, 3), v(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MailboxVersion> v(int... ints) {
|
||||||
|
List<MailboxVersion> versions = new ArrayList<>(ints.length);
|
||||||
|
for (int v : ints) {
|
||||||
|
// minor versions should not matter
|
||||||
|
versions.add(new MailboxVersion(v, random.nextInt(42)));
|
||||||
|
}
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,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;
|
||||||
@@ -35,6 +40,7 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -45,6 +51,7 @@ import java.util.Random;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
@@ -221,6 +228,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 +293,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,
|
||||||
@@ -301,4 +337,13 @@ public class TestUtils {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCryptoStrengthUnlimited() {
|
||||||
|
try {
|
||||||
|
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
||||||
|
== Integer.MAX_VALUE;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ abstract class Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readTag(InputStream in) throws IOException {
|
byte[] readTag(InputStream in) throws IOException {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
read(in, tag);
|
read(in, tag);
|
||||||
return tag;
|
return tag;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
@@ -67,7 +68,15 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
TransportConnectionReader r) {
|
TransportConnectionReader r) {
|
||||||
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
syncSessionFactory, transportPropertyManager, t, r));
|
syncSessionFactory, transportPropertyManager, t, r, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void manageIncomingConnection(TransportId t,
|
||||||
|
TransportConnectionReader r, TagController c) {
|
||||||
|
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
|
||||||
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
|
syncSessionFactory, transportPropertyManager, t, r, c));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,7 +101,16 @@ class ConnectionManagerImpl implements ConnectionManager {
|
|||||||
TransportConnectionWriter w) {
|
TransportConnectionWriter w) {
|
||||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
syncSessionFactory, transportPropertyManager, c, t, w));
|
syncSessionFactory, transportPropertyManager, c, t, w, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void manageOutgoingConnection(ContactId c, TransportId t,
|
||||||
|
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
|
||||||
|
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||||
|
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||||
|
syncSessionFactory, transportPropertyManager, c, t, w,
|
||||||
|
sessionRecord));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.briarproject.bramble.connection;
|
package org.briarproject.bramble.connection;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
|
||||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
@@ -15,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -23,6 +27,8 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
|
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportConnectionReader reader;
|
private final TransportConnectionReader reader;
|
||||||
|
@Nullable
|
||||||
|
private final TagController tagController;
|
||||||
|
|
||||||
IncomingSimplexSyncConnection(KeyManager keyManager,
|
IncomingSimplexSyncConnection(KeyManager keyManager,
|
||||||
ConnectionRegistry connectionRegistry,
|
ConnectionRegistry connectionRegistry,
|
||||||
@@ -30,33 +36,50 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriterFactory streamWriterFactory,
|
StreamWriterFactory streamWriterFactory,
|
||||||
SyncSessionFactory syncSessionFactory,
|
SyncSessionFactory syncSessionFactory,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
TransportId transportId, TransportConnectionReader reader) {
|
TransportId transportId,
|
||||||
|
TransportConnectionReader reader,
|
||||||
|
@Nullable TagController tagController) {
|
||||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||||
streamWriterFactory, syncSessionFactory,
|
streamWriterFactory, syncSessionFactory,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.reader = reader;
|
this.reader = reader;
|
||||||
|
this.tagController = tagController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Read and recognise the tag
|
// Read and recognise the tag
|
||||||
StreamContext ctx = recogniseTag(reader, transportId);
|
byte[] tag;
|
||||||
|
StreamContext ctx;
|
||||||
|
try {
|
||||||
|
tag = readTag(reader.getInputStream());
|
||||||
|
// If we have a tag controller, defer marking the tag as recognised
|
||||||
|
if (tagController == null) {
|
||||||
|
ctx = keyManager.getStreamContext(transportId, tag);
|
||||||
|
} else {
|
||||||
|
ctx = keyManager.getStreamContextOnly(transportId, tag);
|
||||||
|
}
|
||||||
|
} catch (IOException | DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
LOG.info("Unrecognised tag");
|
LOG.info("Unrecognised tag");
|
||||||
onError(false);
|
onError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ContactId contactId = ctx.getContactId();
|
ContactId contactId = ctx.getContactId();
|
||||||
if (contactId == null) {
|
if (contactId == null) {
|
||||||
LOG.warning("Received rendezvous stream, expected contact");
|
LOG.warning("Received rendezvous stream, expected contact");
|
||||||
onError(true);
|
onError(tag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ctx.isHandshakeMode()) {
|
if (ctx.isHandshakeMode()) {
|
||||||
// TODO: Support handshake mode for contacts
|
// TODO: Support handshake mode for contacts
|
||||||
LOG.warning("Received handshake tag, expected rotation mode");
|
LOG.warning("Received handshake tag, expected rotation mode");
|
||||||
onError(true);
|
onError(tag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -65,15 +88,33 @@ class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
LOG.info("Ignoring priority for simplex connection");
|
LOG.info("Ignoring priority for simplex connection");
|
||||||
// Create and run the incoming session
|
// Create and run the incoming session
|
||||||
createIncomingSession(ctx, reader, handler).run();
|
createIncomingSession(ctx, reader, handler).run();
|
||||||
|
// Success
|
||||||
|
markTagAsRecognisedIfRequired(false, tag);
|
||||||
reader.dispose(false, true);
|
reader.dispose(false, true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
onError(true);
|
onError(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError(boolean recognised) {
|
private void onError() {
|
||||||
disposeOnError(reader, recognised);
|
disposeOnError(reader, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onError(byte[] tag) {
|
||||||
|
markTagAsRecognisedIfRequired(true, tag);
|
||||||
|
disposeOnError(reader, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markTagAsRecognisedIfRequired(boolean exception, byte[] tag) {
|
||||||
|
if (tagController != null &&
|
||||||
|
tagController.shouldMarkTagAsRecognised(exception)) {
|
||||||
|
try {
|
||||||
|
keyManager.markTagAsRecognised(transportId, tag);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
@@ -16,6 +17,8 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -26,6 +29,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final TransportConnectionWriter writer;
|
private final TransportConnectionWriter writer;
|
||||||
|
@Nullable
|
||||||
|
private final OutgoingSessionRecord sessionRecord;
|
||||||
|
|
||||||
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
OutgoingSimplexSyncConnection(KeyManager keyManager,
|
||||||
ConnectionRegistry connectionRegistry,
|
ConnectionRegistry connectionRegistry,
|
||||||
@@ -34,13 +39,15 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
SyncSessionFactory syncSessionFactory,
|
SyncSessionFactory syncSessionFactory,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
ContactId contactId, TransportId transportId,
|
ContactId contactId, TransportId transportId,
|
||||||
TransportConnectionWriter writer) {
|
TransportConnectionWriter writer,
|
||||||
|
@Nullable OutgoingSessionRecord sessionRecord) {
|
||||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||||
streamWriterFactory, syncSessionFactory,
|
streamWriterFactory, syncSessionFactory,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
|
this.sessionRecord = sessionRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,10 +78,16 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
// Use eager retransmission if the transport is lossy and cheap
|
if (sessionRecord == null) {
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
// Use eager retransmission if the transport is lossy and cheap
|
||||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
streamWriter);
|
ctx.getTransportId(), w.getMaxLatency(),
|
||||||
|
w.isLossyAndCheap(), streamWriter);
|
||||||
|
} else {
|
||||||
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
|
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
|
||||||
|
sessionRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,16 +163,11 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there are any acks or messages to send to the given
|
* Returns true if there are any acks to send to the given contact.
|
||||||
* contact over a transport with the given maximum latency.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*
|
|
||||||
* @param eager True if messages that are not yet due for retransmission
|
|
||||||
* should be included
|
|
||||||
*/
|
*/
|
||||||
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
|
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
|
||||||
boolean eager) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
@@ -212,6 +207,18 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
boolean containsMessage(T txn, MessageId m) throws DbException;
|
boolean containsMessage(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are any messages to send to the given
|
||||||
|
* contact over a transport with the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*
|
||||||
|
* @param eager True if messages that are not yet due for retransmission
|
||||||
|
* should be included
|
||||||
|
*/
|
||||||
|
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
|
||||||
|
boolean eager) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given pending contact.
|
* Returns true if the database contains the given pending contact.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -406,6 +413,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 +509,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 +518,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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -573,13 +587,16 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
* message is due to be sent to the given contact. The returned value may
|
* message is due to be sent to the given contact over a transport with
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
* the given latency.
|
||||||
* if no messages are scheduled to be sent.
|
* <p>
|
||||||
|
* The returned value may be zero if a message is due to be sent
|
||||||
|
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
long getNextSendTime(T txn, ContactId c, long maxLatency)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* Returns the pending contact with the given ID.
|
||||||
@@ -598,13 +615,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;
|
||||||
@@ -342,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
||||||
long maxLatency, boolean eager) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
return db.containsAcksToSend(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -373,6 +373,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsIdentity(txn, a);
|
return db.containsIdentity(txn, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
|
||||||
|
long maxLatency, boolean eager) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.containsMessagesToSend(txn, c, maxLatency, eager);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsPendingContact(Transaction transaction,
|
public boolean containsPendingContact(Transaction transaction,
|
||||||
PendingContactId p) throws DbException {
|
PendingContactId p) throws DbException {
|
||||||
@@ -424,13 +433,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 +449,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 +488,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 +504,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 +618,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 +741,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();
|
||||||
@@ -794,10 +814,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
public long getNextSendTime(Transaction transaction, ContactId c,
|
||||||
throws DbException {
|
long maxLatency) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getNextSendTime(txn, c);
|
return db.getNextSendTime(txn, c, maxLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1005,7 +1025,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.getGroupVisibility(txn, id).keySet();
|
db.getGroupVisibility(txn, id).keySet();
|
||||||
db.removeGroup(txn, id);
|
db.removeGroup(txn, id);
|
||||||
transaction.attach(new GroupRemovedEvent(g));
|
transaction.attach(new GroupRemovedEvent(g));
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
||||||
|
affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1069,6 +1090,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,8 +1150,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
List<ContactId> affected = Collections.singletonList(c);
|
List<ContactId> affected = singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1163,6 +1198,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();
|
||||||
}
|
}
|
||||||
@@ -1109,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
public boolean containsAcksToSend(Connection txn, ContactId c)
|
||||||
long maxLatency, boolean eager) throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -1122,34 +1160,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
boolean acksToSend = rs.next();
|
boolean acksToSend = rs.next();
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
if (acksToSend) return true;
|
return acksToSend;
|
||||||
if (eager) {
|
|
||||||
sql = "SELECT NULL from statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
} else {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
sql = "SELECT NULL FROM statuses"
|
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
|
||||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
|
||||||
+ " OR ? < maxLatency)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setInt(1, c.getInt());
|
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
|
||||||
ps.setLong(3, now);
|
|
||||||
ps.setLong(4, maxLatency);
|
|
||||||
}
|
|
||||||
rs = ps.executeQuery();
|
|
||||||
boolean messagesToSend = rs.next();
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return messagesToSend;
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
tryToClose(ps, LOG, WARNING);
|
tryToClose(ps, LOG, WARNING);
|
||||||
@@ -1269,6 +1280,46 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsMessagesToSend(Connection txn, ContactId c,
|
||||||
|
long maxLatency, boolean eager) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
if (eager) {
|
||||||
|
String sql = "SELECT NULL from statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
} else {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
String sql = "SELECT NULL FROM statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
|
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||||
|
+ " OR ? < maxLatency)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
ps.setLong(3, now);
|
||||||
|
ps.setLong(4, maxLatency);
|
||||||
|
}
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean messagesToSend = rs.next();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return messagesToSend;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -1879,6 +1930,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 +2303,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 +2324,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 +2341,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 +2355,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 +2472,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);
|
||||||
@@ -2418,12 +2490,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Connection txn, ContactId c)
|
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT expiry FROM statuses"
|
// Are any messages sendable immediately?
|
||||||
|
String sql = "SELECT NULL FROM statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
|
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
ps.setLong(3, maxLatency);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
if (found) return 0;
|
||||||
|
// When is the earliest expiry time (could be in the past)?
|
||||||
|
sql = "SELECT expiry FROM statuses"
|
||||||
+ " 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"
|
||||||
@@ -2527,7 +2615,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 +2635,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 +2793,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;
|
||||||
|
|||||||
@@ -190,6 +190,10 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (state == STOPPING) {
|
||||||
|
LOG.info("Already stopped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
LOG.info("Stopping services");
|
LOG.info("Stopping services");
|
||||||
state = STOPPING;
|
state = STOPPING;
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||||
|
|||||||
@@ -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,43 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for checking whether a mailbox is reachable.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface ConnectivityChecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the checker. Any current connectivity check is cancelled.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a connectivity check if needed and calls the given observer when
|
||||||
|
* the check succeeds. If a check is already running then the observer is
|
||||||
|
* called when the check succeeds. If a connectivity check has recently
|
||||||
|
* succeeded then the observer is called immediately.
|
||||||
|
* <p>
|
||||||
|
* Observers are removed after being called, or when the checker is
|
||||||
|
* {@link #destroy() destroyed}.
|
||||||
|
*/
|
||||||
|
void checkConnectivity(MailboxProperties properties,
|
||||||
|
ConnectivityObserver o);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an observer that was added via
|
||||||
|
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
|
||||||
|
* there are no remaining observers and a connectivity check is running
|
||||||
|
* then the check will be cancelled.
|
||||||
|
*/
|
||||||
|
void removeObserver(ConnectivityObserver o);
|
||||||
|
|
||||||
|
interface ConnectivityObserver {
|
||||||
|
void onConnectivityCheckSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If no more than this much time has elapsed since the last connectivity
|
||||||
|
* check succeeded, consider the result to be fresh and don't check again.
|
||||||
|
* <p>
|
||||||
|
* Package access for testing.
|
||||||
|
*/
|
||||||
|
static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000;
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
protected final Clock clock;
|
||||||
|
private final MailboxApiCaller mailboxApiCaller;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private boolean destroyed = false;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private Cancellable connectivityCheck = null;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private long lastConnectivityCheckSucceeded = 0;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final List<ConnectivityObserver> connectivityObservers =
|
||||||
|
new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link ApiCall} for checking whether the mailbox is
|
||||||
|
* reachable. The {@link ApiCall} should call
|
||||||
|
* {@link #onConnectivityCheckSucceeded(long)} if the check succeeds.
|
||||||
|
*/
|
||||||
|
abstract ApiCall createConnectivityCheckTask(MailboxProperties properties);
|
||||||
|
|
||||||
|
ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) {
|
||||||
|
this.clock = clock;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = true;
|
||||||
|
connectivityObservers.clear();
|
||||||
|
if (connectivityCheck != null) {
|
||||||
|
connectivityCheck.cancel();
|
||||||
|
connectivityCheck = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkConnectivity(MailboxProperties properties,
|
||||||
|
ConnectivityObserver o) {
|
||||||
|
boolean callNow = false;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
if (connectivityCheck == null) {
|
||||||
|
// No connectivity check is running
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
if (now - lastConnectivityCheckSucceeded
|
||||||
|
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
||||||
|
// The last connectivity check is stale, start a new one
|
||||||
|
connectivityObservers.add(o);
|
||||||
|
ApiCall task = createConnectivityCheckTask(properties);
|
||||||
|
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
||||||
|
} else {
|
||||||
|
// The last connectivity check is fresh
|
||||||
|
callNow = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A connectivity check is running, wait for it to succeed
|
||||||
|
connectivityObservers.add(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callNow) o.onConnectivityCheckSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onConnectivityCheckSucceeded(long now) {
|
||||||
|
List<ConnectivityObserver> observers;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
connectivityCheck = null;
|
||||||
|
lastConnectivityCheckSucceeded = now;
|
||||||
|
observers = new ArrayList<>(connectivityObservers);
|
||||||
|
connectivityObservers.clear();
|
||||||
|
}
|
||||||
|
for (ConnectivityObserver o : observers) {
|
||||||
|
o.onConnectivityCheckSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeObserver(ConnectivityObserver o) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
connectivityObservers.remove(o);
|
||||||
|
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
|
||||||
|
connectivityCheck.cancel();
|
||||||
|
connectivityCheck = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactMailboxClient implements MailboxClient {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactMailboxClient.class.getName());
|
||||||
|
|
||||||
|
private final MailboxWorkerFactory workerFactory;
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final TorReachabilityMonitor reachabilityMonitor;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private MailboxWorker uploadWorker = null, downloadWorker = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ContactMailboxClient(MailboxWorkerFactory workerFactory,
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor) {
|
||||||
|
this.workerFactory = workerFactory;
|
||||||
|
this.connectivityChecker = connectivityChecker;
|
||||||
|
this.reachabilityMonitor = reachabilityMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
LOG.info("Started");
|
||||||
|
// Nothing to do until contact is assigned
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("Destroyed");
|
||||||
|
MailboxWorker uploadWorker, downloadWorker;
|
||||||
|
synchronized (lock) {
|
||||||
|
uploadWorker = this.uploadWorker;
|
||||||
|
this.uploadWorker = null;
|
||||||
|
downloadWorker = this.downloadWorker;
|
||||||
|
this.downloadWorker = null;
|
||||||
|
}
|
||||||
|
if (uploadWorker != null) uploadWorker.destroy();
|
||||||
|
if (downloadWorker != null) downloadWorker.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignContactForUpload(ContactId contactId,
|
||||||
|
MailboxProperties properties, MailboxFolderId folderId) {
|
||||||
|
LOG.info("Contact assigned for upload");
|
||||||
|
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
// For a contact's mailbox we should always be uploading to the outbox
|
||||||
|
// assigned to us by the contact
|
||||||
|
if (!folderId.equals(properties.getOutboxId())) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
||||||
|
connectivityChecker, properties, folderId, contactId);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (this.uploadWorker != null) throw new IllegalStateException();
|
||||||
|
this.uploadWorker = uploadWorker;
|
||||||
|
}
|
||||||
|
uploadWorker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deassignContactForUpload(ContactId contactId) {
|
||||||
|
LOG.info("Contact deassigned for upload");
|
||||||
|
MailboxWorker uploadWorker;
|
||||||
|
synchronized (lock) {
|
||||||
|
uploadWorker = this.uploadWorker;
|
||||||
|
this.uploadWorker = null;
|
||||||
|
}
|
||||||
|
if (uploadWorker != null) uploadWorker.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignContactForDownload(ContactId contactId,
|
||||||
|
MailboxProperties properties, MailboxFolderId folderId) {
|
||||||
|
LOG.info("Contact assigned for download");
|
||||||
|
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
// For a contact's mailbox we should always be downloading from the
|
||||||
|
// inbox assigned to us by the contact
|
||||||
|
if (!folderId.equals(properties.getInboxId())) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
MailboxWorker downloadWorker =
|
||||||
|
workerFactory.createDownloadWorkerForContactMailbox(
|
||||||
|
connectivityChecker, reachabilityMonitor, properties);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (this.downloadWorker != null) throw new IllegalStateException();
|
||||||
|
this.downloadWorker = downloadWorker;
|
||||||
|
}
|
||||||
|
downloadWorker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deassignContactForDownload(ContactId contactId) {
|
||||||
|
LOG.info("Contact deassigned for download");
|
||||||
|
MailboxWorker downloadWorker;
|
||||||
|
synchronized (lock) {
|
||||||
|
downloadWorker = this.downloadWorker;
|
||||||
|
this.downloadWorker = null;
|
||||||
|
}
|
||||||
|
if (downloadWorker != null) downloadWorker.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||||
|
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
|
||||||
|
ContactMailboxConnectivityChecker(Clock clock,
|
||||||
|
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
||||||
|
super(clock, mailboxApiCaller);
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||||
|
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
return new SimpleApiCall(() -> {
|
||||||
|
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||||
|
// Call the observers and cache the result
|
||||||
|
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactMailboxDownloadWorker implements MailboxWorker,
|
||||||
|
ConnectivityObserver, TorReachabilityObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the worker is started it waits for a connectivity check, then
|
||||||
|
* starts its first download cycle: checking the inbox, downloading and
|
||||||
|
* deleting any files, and checking again until the inbox is empty.
|
||||||
|
* <p>
|
||||||
|
* The worker then waits for our Tor hidden service to be reachable before
|
||||||
|
* starting its second download cycle. This ensures that if a contact
|
||||||
|
* tried and failed to connect to our hidden service before it was
|
||||||
|
* reachable, and therefore uploaded a file to the mailbox instead, we'll
|
||||||
|
* find the file in the second download cycle.
|
||||||
|
*/
|
||||||
|
private enum State {
|
||||||
|
CREATED,
|
||||||
|
CONNECTIVITY_CHECK,
|
||||||
|
DOWNLOAD_CYCLE_1,
|
||||||
|
WAITING_FOR_TOR,
|
||||||
|
DOWNLOAD_CYCLE_2,
|
||||||
|
FINISHED,
|
||||||
|
DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactMailboxDownloadWorker.class.getName());
|
||||||
|
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final TorReachabilityMonitor torReachabilityMonitor;
|
||||||
|
private final MailboxApiCaller mailboxApiCaller;
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
private final MailboxFileManager mailboxFileManager;
|
||||||
|
private final MailboxProperties mailboxProperties;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private State state = State.CREATED;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private Cancellable apiCall = null;
|
||||||
|
|
||||||
|
ContactMailboxDownloadWorker(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor torReachabilityMonitor,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxProperties mailboxProperties) {
|
||||||
|
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
this.connectivityChecker = connectivityChecker;
|
||||||
|
this.torReachabilityMonitor = torReachabilityMonitor;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.mailboxFileManager = mailboxFileManager;
|
||||||
|
this.mailboxProperties = mailboxProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
LOG.info("Started");
|
||||||
|
synchronized (lock) {
|
||||||
|
// Don't allow the worker to be reused
|
||||||
|
if (state != State.CREATED) return;
|
||||||
|
state = State.CONNECTIVITY_CHECK;
|
||||||
|
}
|
||||||
|
// Avoid leaking observer in case destroy() is called concurrently
|
||||||
|
// before observer is added
|
||||||
|
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||||
|
boolean destroyed;
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = state == State.DESTROYED;
|
||||||
|
}
|
||||||
|
if (destroyed) connectivityChecker.removeObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("Destroyed");
|
||||||
|
Cancellable apiCall;
|
||||||
|
synchronized (lock) {
|
||||||
|
state = State.DESTROYED;
|
||||||
|
apiCall = this.apiCall;
|
||||||
|
this.apiCall = null;
|
||||||
|
}
|
||||||
|
if (apiCall != null) apiCall.cancel();
|
||||||
|
connectivityChecker.removeObserver(this);
|
||||||
|
torReachabilityMonitor.removeObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectivityCheckSucceeded() {
|
||||||
|
LOG.info("Connectivity check succeeded");
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CONNECTIVITY_CHECK) return;
|
||||||
|
state = State.DOWNLOAD_CYCLE_1;
|
||||||
|
// Start first download cycle
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(this::apiCallListInbox));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallListInbox() throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Listing inbox");
|
||||||
|
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
|
||||||
|
requireNonNull(mailboxProperties.getInboxId()));
|
||||||
|
if (files.isEmpty()) onDownloadCycleFinished();
|
||||||
|
else downloadNextFile(new LinkedList<>(files));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadCycleFinished() {
|
||||||
|
boolean addObserver = false;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DOWNLOAD_CYCLE_1) {
|
||||||
|
LOG.info("First download cycle finished");
|
||||||
|
state = State.WAITING_FOR_TOR;
|
||||||
|
apiCall = null;
|
||||||
|
addObserver = true;
|
||||||
|
} else if (state == State.DOWNLOAD_CYCLE_2) {
|
||||||
|
LOG.info("Second download cycle finished");
|
||||||
|
state = State.FINISHED;
|
||||||
|
apiCall = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (addObserver) {
|
||||||
|
// Avoid leaking observer in case destroy() is called concurrently
|
||||||
|
// before observer is added
|
||||||
|
torReachabilityMonitor.addOneShotObserver(this);
|
||||||
|
boolean destroyed;
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = state == State.DESTROYED;
|
||||||
|
}
|
||||||
|
if (destroyed) torReachabilityMonitor.removeObserver(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadNextFile(Queue<MailboxFile> queue) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
MailboxFile file = queue.remove();
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallDownloadFile(MailboxFile file,
|
||||||
|
Queue<MailboxFile> queue) throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
LOG.info("Downloading file");
|
||||||
|
File tempFile = mailboxFileManager.createTempFileForDownload();
|
||||||
|
try {
|
||||||
|
mailboxApi.getFile(mailboxProperties,
|
||||||
|
requireNonNull(mailboxProperties.getInboxId()),
|
||||||
|
file.name, tempFile);
|
||||||
|
} catch (IOException | ApiException e) {
|
||||||
|
if (!tempFile.delete()) {
|
||||||
|
LOG.warning("Failed to delete temporary file");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
mailboxFileManager.handleDownloadedFile(tempFile);
|
||||||
|
deleteFile(file, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mailboxApi.deleteFile(mailboxProperties,
|
||||||
|
requireNonNull(mailboxProperties.getInboxId()), file.name);
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
// Catch this so we can continue to the next file
|
||||||
|
logException(LOG, INFO, e);
|
||||||
|
}
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
// List the inbox again to check for files that may have arrived
|
||||||
|
// while we were downloading
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.DESTROYED) return;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(this::apiCallListInbox));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadNextFile(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTorReachable() {
|
||||||
|
LOG.info("Our Tor hidden service is reachable");
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WAITING_FOR_TOR) return;
|
||||||
|
state = State.DOWNLOAD_CYCLE_2;
|
||||||
|
// Start second download cycle
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(this::apiCallListInbox));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
|||||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -16,8 +18,12 @@ import java.util.List;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
interface MailboxApi {
|
interface MailboxApi {
|
||||||
|
|
||||||
|
List<MailboxVersion> getServerSupports(MailboxProperties properties)
|
||||||
|
throws IOException, ApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the mailbox with the setup token.
|
* Sets up the mailbox with the setup token.
|
||||||
*
|
*
|
||||||
@@ -25,7 +31,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,34 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxApiCaller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum interval between retries in milliseconds.
|
||||||
|
*/
|
||||||
|
long MIN_RETRY_INTERVAL_MS = MINUTES.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum interval between retries in milliseconds.
|
||||||
|
*/
|
||||||
|
long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously calls the given API call on the {@link IoExecutor},
|
||||||
|
* automatically retrying at increasing intervals until the API call
|
||||||
|
* returns false or retries are cancelled.
|
||||||
|
* <p>
|
||||||
|
* This method is safe to call while holding a lock.
|
||||||
|
*
|
||||||
|
* @return A {@link Cancellable} that can be used to cancel any future
|
||||||
|
* retries.
|
||||||
|
*/
|
||||||
|
Cancellable retryWithBackoff(ApiCall apiCall);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously starts the client.
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the client and its workers, cancelling any pending tasks or
|
||||||
|
* retries.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a contact to the client for upload.
|
||||||
|
*/
|
||||||
|
void assignContactForUpload(ContactId c, MailboxProperties properties,
|
||||||
|
MailboxFolderId folderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deassigns a contact from the client for upload.
|
||||||
|
*/
|
||||||
|
void deassignContactForUpload(ContactId c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a contact to the client for download.
|
||||||
|
*/
|
||||||
|
void assignContactForDownload(ContactId c, MailboxProperties properties,
|
||||||
|
MailboxFolderId folderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deassigns a contact from the client for download.
|
||||||
|
*/
|
||||||
|
void deassignContactForDownload(ContactId c);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxFileManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty file for storing a download.
|
||||||
|
*/
|
||||||
|
File createTempFileForDownload() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a file to be uploaded to the given contact and writes any
|
||||||
|
* waiting data to the file. The IDs of any messages sent or acked will
|
||||||
|
* be added to the given {@link OutgoingSessionRecord}.
|
||||||
|
*/
|
||||||
|
File createAndWriteTempFileForUpload(ContactId contactId,
|
||||||
|
OutgoingSessionRecord sessionRecord) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a file that has been downloaded. The file should be created
|
||||||
|
* with {@link #createTempFileForDownload()}.
|
||||||
|
*/
|
||||||
|
void handleDownloadedFile(File f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(MailboxFileManagerImpl.class.getName());
|
||||||
|
|
||||||
|
// Package access for testing
|
||||||
|
static final String DOWNLOAD_DIR_NAME = "downloads";
|
||||||
|
static final String UPLOAD_DIR_NAME = "uploads";
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
private final LifecycleManager lifecycleManager;
|
||||||
|
private final File mailboxDir;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final CountDownLatch orphanLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxFileManagerImpl(@IoExecutor Executor ioExecutor,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
ConnectionManager connectionManager,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
@MailboxDirectory File mailboxDir,
|
||||||
|
EventBus eventBus) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.lifecycleManager = lifecycleManager;
|
||||||
|
this.mailboxDir = mailboxDir;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File createTempFileForDownload() throws IOException {
|
||||||
|
return createTempFile(DOWNLOAD_DIR_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File createAndWriteTempFileForUpload(ContactId contactId,
|
||||||
|
OutgoingSessionRecord sessionRecord) throws IOException {
|
||||||
|
File f = createTempFile(UPLOAD_DIR_NAME);
|
||||||
|
// We shouldn't reach this point until the plugin has been started
|
||||||
|
SimplexPlugin plugin =
|
||||||
|
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||||
|
TransportProperties p = new TransportProperties();
|
||||||
|
p.put(PROP_PATH, f.getAbsolutePath());
|
||||||
|
TransportConnectionWriter writer = plugin.createWriter(p);
|
||||||
|
if (writer == null) {
|
||||||
|
delete(f);
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
MailboxFileWriter decorated = new MailboxFileWriter(writer);
|
||||||
|
LOG.info("Writing file for upload");
|
||||||
|
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
|
||||||
|
sessionRecord);
|
||||||
|
if (decorated.awaitDisposal()) {
|
||||||
|
// An exception was thrown during the session - delete the file
|
||||||
|
delete(f);
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createTempFile(String dirName) throws IOException {
|
||||||
|
// Wait for orphaned files to be handled before creating new files
|
||||||
|
try {
|
||||||
|
orphanLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
File dir = createDirectoryIfNeeded(dirName);
|
||||||
|
return File.createTempFile("mailbox", ".tmp", dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createDirectoryIfNeeded(String name) throws IOException {
|
||||||
|
File dir = new File(mailboxDir, name);
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
dir.mkdirs();
|
||||||
|
if (!dir.isDirectory()) {
|
||||||
|
throw new IOException("Failed to create directory '" + name + "'");
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDownloadedFile(File f) {
|
||||||
|
// We shouldn't reach this point until the plugin has been started
|
||||||
|
SimplexPlugin plugin =
|
||||||
|
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||||
|
TransportProperties p = new TransportProperties();
|
||||||
|
p.put(PROP_PATH, f.getAbsolutePath());
|
||||||
|
TransportConnectionReader reader = plugin.createReader(p);
|
||||||
|
if (reader == null) {
|
||||||
|
LOG.warning("Failed to create reader for downloaded file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TransportConnectionReader decorated = new MailboxFileReader(reader, f);
|
||||||
|
LOG.info("Reading downloaded file");
|
||||||
|
connectionManager.manageIncomingConnection(ID, decorated,
|
||||||
|
exception -> isHandlingComplete(exception, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandlingComplete(boolean exception, boolean recognised) {
|
||||||
|
// If we've successfully read the file then we're done
|
||||||
|
if (!exception && recognised) return true;
|
||||||
|
// If the app is shutting down we may get spurious IO exceptions
|
||||||
|
// due to executors being shut down. Leave the file in the download
|
||||||
|
// directory and we'll try to read it again at the next startup
|
||||||
|
return !lifecycleManager.getLifecycleState().isAfter(RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
// Wait for the transport to become active before handling orphaned
|
||||||
|
// files so that we can get the plugin from the plugin manager
|
||||||
|
if (e instanceof TransportActiveEvent) {
|
||||||
|
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||||
|
if (t.getTransportId().equals(ID)) {
|
||||||
|
ioExecutor.execute(this::handleOrphanedFiles);
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called at startup, as soon as the plugin is started, to
|
||||||
|
* delete any files that were left in the upload directory at the last
|
||||||
|
* shutdown and handle any files that were left in the download directory.
|
||||||
|
*/
|
||||||
|
@IoExecutor
|
||||||
|
private void handleOrphanedFiles() {
|
||||||
|
try {
|
||||||
|
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
|
||||||
|
File[] orphanedUploads = uploadDir.listFiles();
|
||||||
|
if (orphanedUploads != null) {
|
||||||
|
for (File f : orphanedUploads) delete(f);
|
||||||
|
}
|
||||||
|
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
||||||
|
File[] orphanedDownloads = downloadDir.listFiles();
|
||||||
|
// Now that we've got the list of orphaned downloads, new files
|
||||||
|
// can be created in the download directory
|
||||||
|
orphanLatch.countDown();
|
||||||
|
if (orphanedDownloads != null) {
|
||||||
|
for (File f : orphanedDownloads) handleDownloadedFile(f);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MailboxFileReader implements TransportConnectionReader {
|
||||||
|
|
||||||
|
private final TransportConnectionReader delegate;
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
private MailboxFileReader(TransportConnectionReader delegate,
|
||||||
|
File file) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return delegate.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose(boolean exception, boolean recognised)
|
||||||
|
throws IOException {
|
||||||
|
delegate.dispose(exception, recognised);
|
||||||
|
if (isHandlingComplete(exception, recognised)) {
|
||||||
|
LOG.info("Deleting downloaded file");
|
||||||
|
delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MailboxFileWriter
|
||||||
|
implements TransportConnectionWriter {
|
||||||
|
|
||||||
|
private final TransportConnectionWriter delegate;
|
||||||
|
private final BlockingQueue<Boolean> disposalResult =
|
||||||
|
new ArrayBlockingQueue<>(1);
|
||||||
|
|
||||||
|
private MailboxFileWriter(TransportConnectionWriter delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxLatency() {
|
||||||
|
return delegate.getMaxLatency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxIdleTime() {
|
||||||
|
return delegate.getMaxIdleTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLossyAndCheap() {
|
||||||
|
return delegate.isLossyAndCheap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
return delegate.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose(boolean exception) throws IOException {
|
||||||
|
delegate.dispose(exception);
|
||||||
|
disposalResult.add(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the delegate to be disposed and returns true if an
|
||||||
|
* exception occurred.
|
||||||
|
*/
|
||||||
|
private boolean awaitDisposal() {
|
||||||
|
try {
|
||||||
|
return disposalResult.take();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.info("Interrupted while waiting for disposal");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,27 +2,44 @@ package org.briarproject.bramble.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.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||||
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.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxManagerImpl implements MailboxManager {
|
class MailboxManagerImpl implements MailboxManager {
|
||||||
|
|
||||||
|
private static final String TAG = MailboxManagerImpl.class.getName();
|
||||||
|
private final static Logger LOG = getLogger(TAG);
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final MailboxApi api;
|
||||||
|
private final TransactionManager db;
|
||||||
private final MailboxSettingsManager mailboxSettingsManager;
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
private final MailboxPairingTaskFactory pairingTaskFactory;
|
private final MailboxPairingTaskFactory pairingTaskFactory;
|
||||||
|
private final Clock clock;
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -32,11 +49,17 @@ class MailboxManagerImpl implements MailboxManager {
|
|||||||
@Inject
|
@Inject
|
||||||
MailboxManagerImpl(
|
MailboxManagerImpl(
|
||||||
@IoExecutor Executor ioExecutor,
|
@IoExecutor Executor ioExecutor,
|
||||||
|
MailboxApi api,
|
||||||
|
TransactionManager db,
|
||||||
MailboxSettingsManager mailboxSettingsManager,
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
MailboxPairingTaskFactory pairingTaskFactory) {
|
MailboxPairingTaskFactory pairingTaskFactory,
|
||||||
|
Clock clock) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.api = api;
|
||||||
|
this.db = db;
|
||||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
this.pairingTaskFactory = pairingTaskFactory;
|
this.pairingTaskFactory = pairingTaskFactory;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,4 +98,62 @@ class MailboxManagerImpl implements MailboxManager {
|
|||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkConnection() {
|
||||||
|
List<MailboxVersion> versions = null;
|
||||||
|
try {
|
||||||
|
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||||
|
mailboxSettingsManager::getOwnMailboxProperties);
|
||||||
|
if (props == null) throw new DbException();
|
||||||
|
versions = api.getServerSupports(props);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// we don't treat this is a failure to record
|
||||||
|
return false;
|
||||||
|
} catch (IOException | MailboxApi.ApiException e) {
|
||||||
|
// we record this as a failure
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
recordCheckResult(versions);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
return versions != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
|
||||||
|
throws DbException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
db.transaction(false, txn -> {
|
||||||
|
if (versions != null) {
|
||||||
|
mailboxSettingsManager
|
||||||
|
.recordSuccessfulConnection(txn, now, versions);
|
||||||
|
} 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,42 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
|
||||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class MailboxModule {
|
public class MailboxModule {
|
||||||
|
|
||||||
public static class EagerSingletons {
|
public static class EagerSingletons {
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPropertyValidator mailboxPropertyValidator;
|
MailboxUpdateValidator mailboxUpdateValidator;
|
||||||
@Inject
|
@Inject
|
||||||
MailboxPropertyManager mailboxPropertyManager;
|
MailboxUpdateManager mailboxUpdateManager;
|
||||||
|
@Inject
|
||||||
|
MailboxFileManager mailboxFileManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -44,6 +52,7 @@ public class MailboxModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
MailboxSettingsManager provideMailboxSettingsManager(
|
MailboxSettingsManager provideMailboxSettingsManager(
|
||||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||||
return mailboxSettingsManager;
|
return mailboxSettingsManager;
|
||||||
@@ -56,31 +65,60 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
|
||||||
|
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
|
||||||
|
if (featureFlags.shouldEnableMailbox()) {
|
||||||
|
eventBus.addListener(mailboxFileManager);
|
||||||
|
}
|
||||||
|
return mailboxFileManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
MailboxWorkerFactory provideMailboxWorkerFactory(
|
||||||
|
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
|
||||||
|
return mailboxWorkerFactory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,28 +8,34 @@ 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;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@Immutable
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final String SETTINGS_NAMESPACE = "mailbox";
|
static final String SETTINGS_NAMESPACE = "mailbox";
|
||||||
|
// TODO: This currently stores the base URL, not the 56-char onion address
|
||||||
static final String SETTINGS_KEY_ONION = "onion";
|
static final String SETTINGS_KEY_ONION = "onion";
|
||||||
static final String SETTINGS_KEY_TOKEN = "token";
|
static final String SETTINGS_KEY_TOKEN = "token";
|
||||||
|
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
|
||||||
static final String SETTINGS_KEY_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 +61,10 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
String onion = s.get(SETTINGS_KEY_ONION);
|
String onion = s.get(SETTINGS_KEY_ONION);
|
||||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||||
|
List<MailboxVersion> serverSupports = parseServerSupports(s);
|
||||||
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 +76,22 @@ 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();
|
||||||
|
encodeServerSupports(serverSupports, s);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,18 +102,44 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
||||||
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
||||||
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
|
List<MailboxVersion> serverSupports;
|
||||||
|
try {
|
||||||
|
serverSupports = parseServerSupports(s);
|
||||||
|
} catch (DbException e) {
|
||||||
|
serverSupports = emptyList();
|
||||||
|
}
|
||||||
|
return new MailboxStatus(lastAttempt, lastSuccess, attempts,
|
||||||
|
serverSupports);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordSuccessfulConnection(Transaction txn, long now)
|
public void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
recordSuccessfulConnection(txn, now, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordSuccessfulConnection(Transaction txn, long now,
|
||||||
|
@Nullable List<MailboxVersion> versions) throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
|
// fetch version that the server supports first
|
||||||
|
List<MailboxVersion> serverSupports;
|
||||||
|
if (versions == null) {
|
||||||
|
Settings oldSettings =
|
||||||
|
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
serverSupports = parseServerSupports(oldSettings);
|
||||||
|
} else {
|
||||||
|
serverSupports = versions;
|
||||||
|
// store new versions
|
||||||
|
encodeServerSupports(serverSupports, s);
|
||||||
|
}
|
||||||
|
// now record the successful connection
|
||||||
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
MailboxStatus status = new MailboxStatus(now, now, 0);
|
// broadcast status event
|
||||||
|
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +154,11 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
||||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
|
||||||
|
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts,
|
||||||
|
serverSupports);
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
|
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,4 +180,30 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
|||||||
if (isNullOrEmpty(filename)) return null;
|
if (isNullOrEmpty(filename)) return null;
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void encodeServerSupports(List<MailboxVersion> serverSupports,
|
||||||
|
Settings s) {
|
||||||
|
int[] ints = new int[serverSupports.size() * 2];
|
||||||
|
int i = 0;
|
||||||
|
for (MailboxVersion v : serverSupports) {
|
||||||
|
ints[i++] = v.getMajor();
|
||||||
|
ints[i++] = v.getMinor();
|
||||||
|
}
|
||||||
|
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MailboxVersion> parseServerSupports(Settings s)
|
||||||
|
throws DbException {
|
||||||
|
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
|
||||||
|
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
|
||||||
|
// We know we were paired, so we must have proper serverSupports
|
||||||
|
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
|
||||||
|
throw new DbException();
|
||||||
|
}
|
||||||
|
List<MailboxVersion> serverSupports = new ArrayList<>();
|
||||||
|
for (int i = 0; i < ints.length - 1; i += 2) {
|
||||||
|
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
|
||||||
|
}
|
||||||
|
return serverSupports;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,14 @@ 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
|
||||||
|
// messages getting stranded:
|
||||||
|
// - on our mailbox, if they now have a mailbox but didn't before
|
||||||
|
// - on the contact's old mailbox, if they removed their mailbox
|
||||||
|
// - on the contact's old mailbox, if they replaced their mailbox
|
||||||
|
db.resetUnackedMessagesToSend(txn, c);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
@@ -165,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);
|
||||||
@@ -260,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,394 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||||
|
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||||
|
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||||
|
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||||
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
|
||||||
|
EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the worker is started it checks for data to send. If data is ready
|
||||||
|
* to send, the worker waits for a connectivity check, then writes and
|
||||||
|
* uploads a file and checks again for data to send.
|
||||||
|
* <p>
|
||||||
|
* If data is due to be sent at some time in the future, the worker
|
||||||
|
* schedules a wakeup for that time and also listens for events indicating
|
||||||
|
* that new data may be ready to send.
|
||||||
|
* <p>
|
||||||
|
* If there's no data to send, the worker listens for events indicating
|
||||||
|
* that new data may be ready to send.
|
||||||
|
*/
|
||||||
|
private enum State {
|
||||||
|
CREATED,
|
||||||
|
CHECKING_FOR_DATA,
|
||||||
|
WAITING_FOR_DATA,
|
||||||
|
CONNECTIVITY_CHECK,
|
||||||
|
WRITING_UPLOADING,
|
||||||
|
DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(MailboxUploadWorker.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we're waiting for data to send and an event indicates that new data
|
||||||
|
* may have become available, wait this long before checking the DB. This
|
||||||
|
* should help to avoid creating lots of small files when several acks or
|
||||||
|
* messages become available to send in a short period (eg when reading a
|
||||||
|
* file downloaded from a mailbox).
|
||||||
|
* <p>
|
||||||
|
* Package access for testing.
|
||||||
|
*/
|
||||||
|
static final long CHECK_DELAY_MS = 5_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long to wait before retrying when an exception occurs while writing
|
||||||
|
* a file.
|
||||||
|
* <p>
|
||||||
|
* Package access for testing.
|
||||||
|
*/
|
||||||
|
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final Clock clock;
|
||||||
|
private final TaskScheduler taskScheduler;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final MailboxApiCaller mailboxApiCaller;
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
private final MailboxFileManager mailboxFileManager;
|
||||||
|
private final MailboxProperties mailboxProperties;
|
||||||
|
private final MailboxFolderId folderId;
|
||||||
|
private final ContactId contactId;
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private State state = State.CREATED;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private File file = null;
|
||||||
|
|
||||||
|
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
|
||||||
|
DatabaseComponent db,
|
||||||
|
Clock clock,
|
||||||
|
TaskScheduler taskScheduler,
|
||||||
|
EventBus eventBus,
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager,
|
||||||
|
MailboxProperties mailboxProperties,
|
||||||
|
MailboxFolderId folderId,
|
||||||
|
ContactId contactId) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.clock = clock;
|
||||||
|
this.taskScheduler = taskScheduler;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.connectivityChecker = connectivityChecker;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.mailboxFileManager = mailboxFileManager;
|
||||||
|
this.mailboxProperties = mailboxProperties;
|
||||||
|
this.folderId = folderId;
|
||||||
|
this.contactId = contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
LOG.info("Started");
|
||||||
|
synchronized (lock) {
|
||||||
|
// Don't allow the worker to be reused
|
||||||
|
if (state != State.CREATED) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
}
|
||||||
|
ioExecutor.execute(this::checkForDataToSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("Destroyed");
|
||||||
|
Cancellable wakeupTask, checkTask, apiCall;
|
||||||
|
File file;
|
||||||
|
synchronized (lock) {
|
||||||
|
state = State.DESTROYED;
|
||||||
|
wakeupTask = this.wakeupTask;
|
||||||
|
this.wakeupTask = null;
|
||||||
|
checkTask = this.checkTask;
|
||||||
|
this.checkTask = null;
|
||||||
|
apiCall = this.apiCall;
|
||||||
|
this.apiCall = null;
|
||||||
|
file = this.file;
|
||||||
|
this.file = null;
|
||||||
|
}
|
||||||
|
if (wakeupTask != null) wakeupTask.cancel();
|
||||||
|
if (checkTask != null) checkTask.cancel();
|
||||||
|
if (apiCall != null) apiCall.cancel();
|
||||||
|
if (file != null) delete(file);
|
||||||
|
connectivityChecker.removeObserver(this);
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void checkForDataToSend() {
|
||||||
|
synchronized (lock) {
|
||||||
|
checkTask = null;
|
||||||
|
if (state != State.CHECKING_FOR_DATA) return;
|
||||||
|
}
|
||||||
|
LOG.info("Checking for data to send");
|
||||||
|
try {
|
||||||
|
db.transaction(true, txn -> {
|
||||||
|
long nextSendTime;
|
||||||
|
if (db.containsAcksToSend(txn, contactId)) {
|
||||||
|
nextSendTime = 0L;
|
||||||
|
} else {
|
||||||
|
nextSendTime = db.getNextSendTime(txn, contactId,
|
||||||
|
MAX_LATENCY);
|
||||||
|
}
|
||||||
|
// Handle the result on the event executor to avoid races with
|
||||||
|
// incoming events
|
||||||
|
txn.attach(() -> handleNextSendTime(nextSendTime));
|
||||||
|
});
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void handleNextSendTime(long nextSendTime) {
|
||||||
|
if (nextSendTime == Long.MAX_VALUE) {
|
||||||
|
// Nothing is sendable now or due to be sent in the future. Wait
|
||||||
|
// for an event indicating that new data may be ready to send
|
||||||
|
waitForDataToSend();
|
||||||
|
} else {
|
||||||
|
// Work out the delay until data's ready to send (may be negative)
|
||||||
|
long delay = nextSendTime - clock.currentTimeMillis();
|
||||||
|
if (delay > 0) {
|
||||||
|
// Schedule a wakeup when data will be ready to send. If an
|
||||||
|
// event is received in the meantime indicating that new data
|
||||||
|
// may be ready to send, we'll cancel the wakeup
|
||||||
|
scheduleWakeup(delay);
|
||||||
|
} else {
|
||||||
|
// Data is ready to send now
|
||||||
|
checkConnectivity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void waitForDataToSend() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CHECKING_FOR_DATA) return;
|
||||||
|
state = State.WAITING_FOR_DATA;
|
||||||
|
LOG.info("Waiting for data to send");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void scheduleWakeup(long delay) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CHECKING_FOR_DATA) return;
|
||||||
|
state = State.WAITING_FOR_DATA;
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Scheduling wakeup in " + delay + " ms");
|
||||||
|
}
|
||||||
|
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
|
||||||
|
delay, MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void wakeUp() {
|
||||||
|
LOG.info("Woke up");
|
||||||
|
synchronized (lock) {
|
||||||
|
wakeupTask = null;
|
||||||
|
if (state != State.WAITING_FOR_DATA) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
}
|
||||||
|
checkForDataToSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void checkConnectivity() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CHECKING_FOR_DATA) return;
|
||||||
|
state = State.CONNECTIVITY_CHECK;
|
||||||
|
}
|
||||||
|
LOG.info("Checking connectivity");
|
||||||
|
// Avoid leaking observer in case destroy() is called concurrently
|
||||||
|
// before observer is added
|
||||||
|
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||||
|
boolean destroyed;
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = state == State.DESTROYED;
|
||||||
|
}
|
||||||
|
if (destroyed) connectivityChecker.removeObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectivityCheckSucceeded() {
|
||||||
|
LOG.info("Connectivity check succeeded");
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CONNECTIVITY_CHECK) return;
|
||||||
|
state = State.WRITING_UPLOADING;
|
||||||
|
}
|
||||||
|
ioExecutor.execute(this::writeAndUploadFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void writeAndUploadFile() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WRITING_UPLOADING) return;
|
||||||
|
}
|
||||||
|
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||||
|
File file;
|
||||||
|
try {
|
||||||
|
file = mailboxFileManager.createAndWriteTempFileForUpload(
|
||||||
|
contactId, sessionRecord);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// Try again after a delay
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WRITING_UPLOADING) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||||
|
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean deleteFile = false;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state == State.WRITING_UPLOADING) {
|
||||||
|
this.file = file;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(() -> apiCallUploadFile(file,
|
||||||
|
sessionRecord)));
|
||||||
|
} else {
|
||||||
|
deleteFile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deleteFile) delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void apiCallUploadFile(File file,
|
||||||
|
OutgoingSessionRecord sessionRecord)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WRITING_UPLOADING) return;
|
||||||
|
}
|
||||||
|
LOG.info("Uploading file");
|
||||||
|
mailboxApi.addFile(mailboxProperties, folderId, file);
|
||||||
|
markMessagesSentOrAcked(sessionRecord);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WRITING_UPLOADING) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
apiCall = null;
|
||||||
|
this.file = null;
|
||||||
|
}
|
||||||
|
delete(file);
|
||||||
|
checkForDataToSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
|
||||||
|
Collection<MessageId> acked = sessionRecord.getAckedIds();
|
||||||
|
Collection<MessageId> sent = sessionRecord.getSentIds();
|
||||||
|
try {
|
||||||
|
db.transaction(false, txn -> {
|
||||||
|
if (!acked.isEmpty()) {
|
||||||
|
db.setAckSent(txn, contactId, acked);
|
||||||
|
}
|
||||||
|
if (!sent.isEmpty()) {
|
||||||
|
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof MessageToAckEvent) {
|
||||||
|
MessageToAckEvent m = (MessageToAckEvent) e;
|
||||||
|
if (m.getContactId().equals(contactId)) {
|
||||||
|
LOG.info("Message to ack");
|
||||||
|
onDataToSend();
|
||||||
|
}
|
||||||
|
} else if (e instanceof MessageSharedEvent) {
|
||||||
|
LOG.info("Message shared");
|
||||||
|
onDataToSend();
|
||||||
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
|
if (g.getVisibility() == SHARED &&
|
||||||
|
g.getAffectedContacts().contains(contactId)) {
|
||||||
|
LOG.info("Group shared");
|
||||||
|
onDataToSend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void onDataToSend() {
|
||||||
|
Cancellable wakeupTask;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.WAITING_FOR_DATA) return;
|
||||||
|
state = State.CHECKING_FOR_DATA;
|
||||||
|
wakeupTask = this.wakeupTask;
|
||||||
|
this.wakeupTask = null;
|
||||||
|
// Delay the check to avoid creating lots of small files
|
||||||
|
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||||
|
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
// If we had scheduled a wakeup when data was due to be sent, cancel it
|
||||||
|
if (wakeupTask != null) wakeupTask.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A worker that downloads files from a contact's mailbox.
|
||||||
|
*/
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxWorker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously starts the worker.
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the worker and cancels any pending tasks or retries.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MailboxWorkerFactory {
|
||||||
|
|
||||||
|
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties, MailboxFolderId folderId,
|
||||||
|
ContactId contactId);
|
||||||
|
|
||||||
|
MailboxWorker createDownloadWorkerForContactMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties);
|
||||||
|
|
||||||
|
MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties);
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final Clock clock;
|
||||||
|
private final TaskScheduler taskScheduler;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final MailboxApiCaller mailboxApiCaller;
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
private final MailboxFileManager mailboxFileManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
|
||||||
|
DatabaseComponent db,
|
||||||
|
Clock clock,
|
||||||
|
TaskScheduler taskScheduler,
|
||||||
|
EventBus eventBus,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxFileManager mailboxFileManager) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.clock = clock;
|
||||||
|
this.taskScheduler = taskScheduler;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.mailboxFileManager = mailboxFileManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxWorker createUploadWorker(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties, MailboxFolderId folderId,
|
||||||
|
ContactId contactId) {
|
||||||
|
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
|
||||||
|
clock, taskScheduler, eventBus, connectivityChecker,
|
||||||
|
mailboxApiCaller, mailboxApi, mailboxFileManager,
|
||||||
|
properties, folderId, contactId);
|
||||||
|
eventBus.addListener(worker);
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxWorker createDownloadWorkerForContactMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
return new ContactMailboxDownloadWorker(connectivityChecker,
|
||||||
|
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||||
|
mailboxFileManager, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
// TODO
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(OwnMailboxConnectivityChecker.class.getName());
|
||||||
|
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
private final TransactionManager db;
|
||||||
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
|
|
||||||
|
OwnMailboxConnectivityChecker(Clock clock,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
TransactionManager db,
|
||||||
|
MailboxSettingsManager mailboxSettingsManager) {
|
||||||
|
super(clock, mailboxApiCaller);
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.db = db;
|
||||||
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||||
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
return () -> {
|
||||||
|
try {
|
||||||
|
return checkConnectivityAndStoreResult(properties);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return true; // Retry
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkConnectivityAndStoreResult(
|
||||||
|
MailboxProperties properties) throws DbException {
|
||||||
|
try {
|
||||||
|
List<MailboxVersion> serverSupports =
|
||||||
|
mailboxApi.getServerSupports(properties);
|
||||||
|
LOG.info("Own mailbox is reachable");
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
db.transaction(false, txn -> mailboxSettingsManager
|
||||||
|
.recordSuccessfulConnection(txn, now, serverSupports));
|
||||||
|
// Call the observers and cache the result
|
||||||
|
onConnectivityCheckSucceeded(now);
|
||||||
|
return false; // Don't retry
|
||||||
|
} catch (IOException | ApiException e) {
|
||||||
|
LOG.warning("Own mailbox is unreachable");
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
db.transaction(false, txn -> mailboxSettingsManager
|
||||||
|
.recordFailedConnectionAttempt(txn, now));
|
||||||
|
}
|
||||||
|
return true; // Retry
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class OwnMailboxContactListWorker
|
||||||
|
implements MailboxWorker, ConnectivityObserver, EventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the worker is started it waits for a connectivity check, then
|
||||||
|
* fetches the remote contact list and compares it to the local contact
|
||||||
|
* list.
|
||||||
|
* <p>
|
||||||
|
* Any contacts that are missing from the remote list are added to the
|
||||||
|
* mailbox's contact list, while any contacts that are missing from the
|
||||||
|
* local list are removed from the mailbox's contact list.
|
||||||
|
* <p>
|
||||||
|
* Once the remote contact list has been brought up to date, the worker
|
||||||
|
* waits for events indicating that contacts have been added or removed.
|
||||||
|
* Each time an event is received, the worker updates the mailbox's
|
||||||
|
* contact list and then goes back to waiting.
|
||||||
|
*/
|
||||||
|
private enum State {
|
||||||
|
CREATED,
|
||||||
|
CONNECTIVITY_CHECK,
|
||||||
|
FETCHING_CONTACT_LIST,
|
||||||
|
UPDATING_CONTACT_LIST,
|
||||||
|
WAITING_FOR_CHANGES,
|
||||||
|
DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(OwnMailboxContactListWorker.class.getName());
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final ConnectivityChecker connectivityChecker;
|
||||||
|
private final MailboxApiCaller mailboxApiCaller;
|
||||||
|
private final MailboxApi mailboxApi;
|
||||||
|
private final MailboxUpdateManager mailboxUpdateManager;
|
||||||
|
private final MailboxProperties mailboxProperties;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private State state = State.CREATED;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private Cancellable apiCall = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A queue of updates waiting to be applied to the remote contact list.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final Queue<Update> updates = new LinkedList<>();
|
||||||
|
|
||||||
|
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
|
||||||
|
DatabaseComponent db,
|
||||||
|
EventBus eventBus,
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxApiCaller mailboxApiCaller,
|
||||||
|
MailboxApi mailboxApi,
|
||||||
|
MailboxUpdateManager mailboxUpdateManager,
|
||||||
|
MailboxProperties mailboxProperties) {
|
||||||
|
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.connectivityChecker = connectivityChecker;
|
||||||
|
this.mailboxApiCaller = mailboxApiCaller;
|
||||||
|
this.mailboxApi = mailboxApi;
|
||||||
|
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||||
|
this.mailboxProperties = mailboxProperties;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
LOG.info("Started");
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CREATED) return;
|
||||||
|
state = State.CONNECTIVITY_CHECK;
|
||||||
|
}
|
||||||
|
// Avoid leaking observer in case destroy() is called concurrently
|
||||||
|
// before observer is added
|
||||||
|
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||||
|
boolean destroyed;
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = state == State.DESTROYED;
|
||||||
|
}
|
||||||
|
if (destroyed) connectivityChecker.removeObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
LOG.info("Destroyed");
|
||||||
|
Cancellable apiCall;
|
||||||
|
synchronized (lock) {
|
||||||
|
state = State.DESTROYED;
|
||||||
|
apiCall = this.apiCall;
|
||||||
|
this.apiCall = null;
|
||||||
|
}
|
||||||
|
if (apiCall != null) apiCall.cancel();
|
||||||
|
connectivityChecker.removeObserver(this);
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectivityCheckSucceeded() {
|
||||||
|
LOG.info("Connectivity check succeeded");
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.CONNECTIVITY_CHECK) return;
|
||||||
|
state = State.FETCHING_CONTACT_LIST;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||||
|
new SimpleApiCall(this::apiCallFetchContactList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void apiCallFetchContactList() throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||||
|
}
|
||||||
|
LOG.info("Fetching remote contact list");
|
||||||
|
Collection<ContactId> remote =
|
||||||
|
mailboxApi.getContacts(mailboxProperties);
|
||||||
|
ioExecutor.execute(() -> loadLocalContactList(remote));
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void loadLocalContactList(Collection<ContactId> remote) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||||
|
apiCall = null;
|
||||||
|
}
|
||||||
|
LOG.info("Loading local contact list");
|
||||||
|
try {
|
||||||
|
db.transaction(true, txn -> {
|
||||||
|
Collection<Contact> local = db.getContacts(txn);
|
||||||
|
// Handle the result on the event executor to avoid races with
|
||||||
|
// incoming events
|
||||||
|
txn.attach(() -> reconcileContactLists(local, remote));
|
||||||
|
});
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void reconcileContactLists(Collection<Contact> local,
|
||||||
|
Collection<ContactId> remote) {
|
||||||
|
Set<ContactId> localIds = new HashSet<>();
|
||||||
|
for (Contact c : local) localIds.add(c.getId());
|
||||||
|
remote = new HashSet<>(remote);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||||
|
for (ContactId c : localIds) {
|
||||||
|
if (!remote.contains(c)) updates.add(new Update(true, c));
|
||||||
|
}
|
||||||
|
for (ContactId c : remote) {
|
||||||
|
if (!localIds.contains(c)) updates.add(new Update(false, c));
|
||||||
|
}
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
LOG.info("Contact list is up to date");
|
||||||
|
state = State.WAITING_FOR_CHANGES;
|
||||||
|
} else {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(updates.size() + " updates to apply");
|
||||||
|
}
|
||||||
|
state = State.UPDATING_CONTACT_LIST;
|
||||||
|
ioExecutor.execute(this::updateContactList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void updateContactList() {
|
||||||
|
Update update;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
update = updates.poll();
|
||||||
|
if (update == null) {
|
||||||
|
LOG.info("No more updates to process");
|
||||||
|
state = State.WAITING_FOR_CHANGES;
|
||||||
|
apiCall = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update.add) loadMailboxProperties(update.contactId);
|
||||||
|
else removeContact(update.contactId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void loadMailboxProperties(ContactId c) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
}
|
||||||
|
LOG.info("Loading mailbox properties for contact");
|
||||||
|
try {
|
||||||
|
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
|
||||||
|
mailboxUpdateManager.getLocalUpdate(txn, c));
|
||||||
|
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
|
||||||
|
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
|
||||||
|
} else {
|
||||||
|
// Our own mailbox was concurrently unpaired. This worker will
|
||||||
|
// be destroyed soon, so we can stop here
|
||||||
|
LOG.info("Own mailbox was unpaired");
|
||||||
|
}
|
||||||
|
} catch (NoSuchContactException e) {
|
||||||
|
// Contact was removed concurrently. Move on to the next update.
|
||||||
|
// Later we may process a removal update for this contact, which
|
||||||
|
// was never added to the mailbox's contact list. The removal API
|
||||||
|
// call should fail safely with a TolerableFailureException
|
||||||
|
LOG.info("No such contact");
|
||||||
|
updateContactList();
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
|
||||||
|
MailboxProperties props = withMailbox.getMailboxProperties();
|
||||||
|
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
|
||||||
|
requireNonNull(props.getInboxId()),
|
||||||
|
requireNonNull(props.getOutboxId()));
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||||
|
apiCallAddContact(contact)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void apiCallAddContact(MailboxContact contact)
|
||||||
|
throws IOException, ApiException, TolerableFailureException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
}
|
||||||
|
LOG.info("Adding contact to remote contact list");
|
||||||
|
mailboxApi.addContact(mailboxProperties, contact);
|
||||||
|
updateContactList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void removeContact(ContactId c) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||||
|
apiCallRemoveContact(c)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void apiCallRemoveContact(ContactId c)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||||
|
}
|
||||||
|
LOG.info("Removing contact from remote contact list");
|
||||||
|
try {
|
||||||
|
mailboxApi.deleteContact(mailboxProperties, c);
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
// Catch this so we can continue to the next update
|
||||||
|
logException(LOG, INFO, e);
|
||||||
|
}
|
||||||
|
updateContactList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof ContactAddedEvent) {
|
||||||
|
LOG.info("Contact added");
|
||||||
|
onContactAdded(((ContactAddedEvent) e).getContactId());
|
||||||
|
} else if (e instanceof ContactRemovedEvent) {
|
||||||
|
LOG.info("Contact removed");
|
||||||
|
onContactRemoved(((ContactRemovedEvent) e).getContactId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void onContactAdded(ContactId c) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST &&
|
||||||
|
state != State.WAITING_FOR_CHANGES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updates.add(new Update(true, c));
|
||||||
|
if (state == State.WAITING_FOR_CHANGES) {
|
||||||
|
state = State.UPDATING_CONTACT_LIST;
|
||||||
|
ioExecutor.execute(this::updateContactList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventExecutor
|
||||||
|
private void onContactRemoved(ContactId c) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (state != State.UPDATING_CONTACT_LIST &&
|
||||||
|
state != State.WAITING_FOR_CHANGES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updates.add(new Update(false, c));
|
||||||
|
if (state == State.WAITING_FOR_CHANGES) {
|
||||||
|
state = State.UPDATING_CONTACT_LIST;
|
||||||
|
ioExecutor.execute(this::updateContactList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An update that should be applied to the remote contact list.
|
||||||
|
*/
|
||||||
|
private static class Update {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the contact should be added, false if the contact should be
|
||||||
|
* removed.
|
||||||
|
*/
|
||||||
|
private final boolean add;
|
||||||
|
private final ContactId contactId;
|
||||||
|
|
||||||
|
private Update(boolean add, ContactId contactId) {
|
||||||
|
this.add = add;
|
||||||
|
this.contactId = contactId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience class for making simple API calls that don't return values.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
class SimpleApiCall implements ApiCall {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
|
||||||
|
|
||||||
|
private final Attempt attempt;
|
||||||
|
|
||||||
|
SimpleApiCall(Attempt attempt) {
|
||||||
|
this.attempt = attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean callApi() {
|
||||||
|
try {
|
||||||
|
attempt.tryToCallApi();
|
||||||
|
return false; // Succeeded, don't retry
|
||||||
|
} catch (IOException | ApiException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return true; // Failed, retry with backoff
|
||||||
|
} catch (TolerableFailureException e) {
|
||||||
|
logException(LOG, INFO, e);
|
||||||
|
return false; // Failed tolerably, don't retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Attempt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a single attempt to call an API endpoint. If this method
|
||||||
|
* throws an {@link IOException} or an {@link ApiException}, the call
|
||||||
|
* will be retried. If it throws a {@link TolerableFailureException}
|
||||||
|
* or returns without throwing an exception, the call will not be
|
||||||
|
* retried.
|
||||||
|
*/
|
||||||
|
void tryToCallApi()
|
||||||
|
throws IOException, ApiException, TolerableFailureException;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Plugin;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
interface TorReachabilityMonitor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long the Tor plugin needs to be continuously
|
||||||
|
* {@link Plugin.State#ACTIVE active} before we assume our contacts can
|
||||||
|
* reach our hidden service.
|
||||||
|
*/
|
||||||
|
long REACHABILITY_PERIOD_MS = MINUTES.toMillis(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the monitor.
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the monitor.
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an observer that will be called when our Tor hidden service becomes
|
||||||
|
* reachable. If our hidden service is already reachable, the observer is
|
||||||
|
* called immediately.
|
||||||
|
* <p>
|
||||||
|
* Observers are removed after being called, or when the monitor is
|
||||||
|
* {@link #destroy() destroyed}.
|
||||||
|
*/
|
||||||
|
void addOneShotObserver(TorReachabilityObserver o);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an observer that was added via
|
||||||
|
* {@link #addOneShotObserver(TorReachabilityObserver)}.
|
||||||
|
*/
|
||||||
|
void removeObserver(TorReachabilityObserver o);
|
||||||
|
|
||||||
|
interface TorReachabilityObserver {
|
||||||
|
|
||||||
|
void onTorReachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Cancellable;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Plugin;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class TorReachabilityMonitorImpl
|
||||||
|
implements TorReachabilityMonitor, EventListener {
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final TaskScheduler taskScheduler;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private boolean reachable = false, destroyed = false;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private final List<TorReachabilityObserver> observers = new ArrayList<>();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private Cancellable task = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TorReachabilityMonitorImpl(
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
TaskScheduler taskScheduler,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
EventBus eventBus) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.taskScheduler = taskScheduler;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
eventBus.addListener(this);
|
||||||
|
Plugin plugin = pluginManager.getPlugin(ID);
|
||||||
|
if (plugin != null && plugin.getState() == ACTIVE) onTorActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
synchronized (lock) {
|
||||||
|
destroyed = true;
|
||||||
|
if (task != null) task.cancel();
|
||||||
|
task = null;
|
||||||
|
observers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addOneShotObserver(TorReachabilityObserver o) {
|
||||||
|
boolean callNow = false;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
if (reachable) callNow = true;
|
||||||
|
else observers.add(o);
|
||||||
|
}
|
||||||
|
if (callNow) o.onTorReachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeObserver(TorReachabilityObserver o) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
observers.remove(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof TransportActiveEvent) {
|
||||||
|
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||||
|
if (t.getTransportId().equals(ID)) onTorActive();
|
||||||
|
} else if (e instanceof TransportInactiveEvent) {
|
||||||
|
TransportInactiveEvent t = (TransportInactiveEvent) e;
|
||||||
|
if (t.getTransportId().equals(ID)) onTorInactive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTorActive() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed || task != null) return;
|
||||||
|
task = taskScheduler.schedule(this::onTorReachable, ioExecutor,
|
||||||
|
REACHABILITY_PERIOD_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTorInactive() {
|
||||||
|
synchronized (lock) {
|
||||||
|
reachable = false;
|
||||||
|
if (task != null) task.cancel();
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void onTorReachable() {
|
||||||
|
List<TorReachabilityObserver> observers;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (destroyed) return;
|
||||||
|
reachable = true;
|
||||||
|
observers = new ArrayList<>(this.observers);
|
||||||
|
this.observers.clear();
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
for (TorReachabilityObserver o : observers) o.onTorReachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
|||||||
public void start() {
|
public void start() {
|
||||||
callback.mergeLocalProperties(
|
callback.mergeLocalProperties(
|
||||||
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
|
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
|
||||||
|
callback.pluginStateChanged(ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -29,11 +30,6 @@ abstract class FilePlugin implements SimplexPlugin {
|
|||||||
protected final PluginCallback callback;
|
protected final PluginCallback callback;
|
||||||
protected final long maxLatency;
|
protected final long maxLatency;
|
||||||
|
|
||||||
protected abstract void writerFinished(File f, boolean exception);
|
|
||||||
|
|
||||||
protected abstract void readerFinished(File f, boolean exception,
|
|
||||||
boolean recognised);
|
|
||||||
|
|
||||||
FilePlugin(PluginCallback callback, long maxLatency) {
|
FilePlugin(PluginCallback callback, long maxLatency) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
@@ -50,9 +46,8 @@ abstract class FilePlugin implements SimplexPlugin {
|
|||||||
String path = p.get(PROP_PATH);
|
String path = p.get(PROP_PATH);
|
||||||
if (isNullOrEmpty(path)) return null;
|
if (isNullOrEmpty(path)) return null;
|
||||||
try {
|
try {
|
||||||
File file = new File(path);
|
FileInputStream in = new FileInputStream(path);
|
||||||
FileInputStream in = new FileInputStream(file);
|
return new TransportInputStreamReader(in);
|
||||||
return new FileTransportReader(file, in, this);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return null;
|
return null;
|
||||||
@@ -70,8 +65,8 @@ abstract class FilePlugin implements SimplexPlugin {
|
|||||||
LOG.info("Failed to create file");
|
LOG.info("Failed to create file");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
FileOutputStream out = new FileOutputStream(file);
|
OutputStream out = new FileOutputStream(file);
|
||||||
return new FileTransportWriter(file, out, this);
|
return new TransportOutputStreamWriter(this, out);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class FileTransportReader implements TransportConnectionReader {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(FileTransportReader.class.getName());
|
|
||||||
|
|
||||||
private final File file;
|
|
||||||
private final InputStream in;
|
|
||||||
private final FilePlugin plugin;
|
|
||||||
|
|
||||||
FileTransportReader(File file, InputStream in, FilePlugin plugin) {
|
|
||||||
this.file = file;
|
|
||||||
this.in = in;
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose(boolean exception, boolean recognised) {
|
|
||||||
tryToClose(in, LOG, WARNING);
|
|
||||||
plugin.readerFinished(file, exception, recognised);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.file;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class FileTransportWriter implements TransportConnectionWriter {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(FileTransportWriter.class.getName());
|
|
||||||
|
|
||||||
private final File file;
|
|
||||||
private final OutputStream out;
|
|
||||||
private final FilePlugin plugin;
|
|
||||||
|
|
||||||
FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
|
|
||||||
this.file = file;
|
|
||||||
this.out = out;
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaxLatency() {
|
|
||||||
return plugin.getMaxLatency();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
return plugin.getMaxIdleTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLossyAndCheap() {
|
|
||||||
return plugin.isLossyAndCheap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getOutputStream() {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose(boolean exception) {
|
|
||||||
tryToClose(out, LOG, WARNING);
|
|
||||||
plugin.writerFinished(file, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginException;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxPlugin extends FilePlugin {
|
||||||
|
|
||||||
|
MailboxPlugin(PluginCallback callback, long maxLatency) {
|
||||||
|
super(callback, maxLatency);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportId getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxIdleTime() {
|
||||||
|
// Unused for simplex transports
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws PluginException {
|
||||||
|
callback.pluginStateChanged(ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() throws PluginException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public State getState() {
|
||||||
|
return ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getReasonsDisabled() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldPoll() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPollingInterval() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void poll(
|
||||||
|
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLossyAndCheap() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxPluginFactory implements SimplexPluginFactory {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxPluginFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportId getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxLatency() {
|
||||||
|
return MAX_LATENCY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||||
|
return new MailboxPlugin(callback, MAX_LATENCY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,8 @@ class RemovableDriveManagerImpl
|
|||||||
@Override
|
@Override
|
||||||
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
||||||
return db.transactionWithResult(true, txn ->
|
return db.transactionWithResult(true, txn ->
|
||||||
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
|
db.containsAcksToSend(txn, c) ||
|
||||||
|
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public interface CircumventionProvider {
|
|||||||
* Countries where bridge connections are likely to work.
|
* Countries where bridge connections are likely to work.
|
||||||
* Should be a subset of {@link #BLOCKED} and the union of
|
* Should be a subset of {@link #BLOCKED} and the union of
|
||||||
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
||||||
* {@link #MEEK_BRIDGES}.
|
* {@link #DPI_BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||||
|
|
||||||
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
|
|||||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
|
* Countries where vanilla bridges are blocked via DPI but non-default
|
||||||
* Should be a subset of {@link #BRIDGES}.
|
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] MEEK_BRIDGES = {"CN", "IR"};
|
String[] DPI_BRIDGES = {"CN", "IR"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if vanilla Tor connections are blocked in the given country.
|
* Returns true if vanilla Tor connections are blocked in the given country.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
@@ -35,8 +34,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
new HashSet<>(asList(DEFAULT_BRIDGES));
|
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||||
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||||
private static final Set<String> MEEK_COUNTRIES =
|
private static final Set<String> DPI_COUNTRIES =
|
||||||
new HashSet<>(asList(MEEK_BRIDGES));
|
new HashSet<>(asList(DPI_BRIDGES));
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CircumventionProviderImpl() {
|
CircumventionProviderImpl() {
|
||||||
@@ -58,8 +57,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
return asList(DEFAULT_OBFS4, VANILLA);
|
return asList(DEFAULT_OBFS4, VANILLA);
|
||||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||||
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
} else if (DPI_COUNTRIES.contains(countryCode)) {
|
||||||
return singletonList(MEEK);
|
return asList(NON_DEFAULT_OBFS4, MEEK);
|
||||||
} else {
|
} else {
|
||||||
return asList(DEFAULT_OBFS4, VANILLA);
|
return asList(DEFAULT_OBFS4, VANILLA);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -106,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
|||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private static final String[] EVENTS = {
|
private static final String[] EVENTS = {
|
||||||
"CIRC",
|
"CIRC",
|
||||||
@@ -123,7 +124,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||||
|
|
||||||
private final Executor ioExecutor, wakefulIoExecutor;
|
protected final Executor ioExecutor;
|
||||||
|
private final Executor wakefulIoExecutor;
|
||||||
private final Executor connectionStatusExecutor;
|
private final Executor connectionStatusExecutor;
|
||||||
private final NetworkManager networkManager;
|
private final NetworkManager networkManager;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
@@ -253,34 +255,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Map<String, String> env = pb.environment();
|
Map<String, String> env = pb.environment();
|
||||||
env.put("HOME", torDirectory.getAbsolutePath());
|
env.put("HOME", torDirectory.getAbsolutePath());
|
||||||
pb.directory(torDirectory);
|
pb.directory(torDirectory);
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
try {
|
try {
|
||||||
torProcess = pb.start();
|
torProcess = pb.start();
|
||||||
} catch (SecurityException | IOException e) {
|
} catch (SecurityException | IOException e) {
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
// Log the process's standard output until it detaches
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
|
||||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
|
||||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
|
||||||
if (stdout.hasNextLine()) {
|
|
||||||
LOG.info(stdout.nextLine());
|
|
||||||
}
|
|
||||||
if (stderr.hasNextLine()) {
|
|
||||||
LOG.info(stderr.nextLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stdout.close();
|
|
||||||
stderr.close();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
// Wait for the process to detach or exit
|
// Wait for the Tor process to start
|
||||||
int exit = torProcess.waitFor();
|
waitForTorToStart(torProcess);
|
||||||
if (exit != 0) {
|
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.warning("Tor exited with value " + exit);
|
|
||||||
throw new PluginException();
|
|
||||||
}
|
|
||||||
// Wait for the auth cookie file to be created/updated
|
// Wait for the auth cookie file to be created/updated
|
||||||
long start = clock.currentTimeMillis();
|
long start = clock.currentTimeMillis();
|
||||||
while (cookieFile.length() < 32) {
|
while (cookieFile.length() < 32) {
|
||||||
@@ -321,6 +304,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);
|
||||||
}
|
}
|
||||||
@@ -394,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return zin;
|
return zin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void append(StringBuilder strb, String name, int value) {
|
private static void append(StringBuilder strb, String name, Object value) {
|
||||||
strb.append(name);
|
strb.append(name);
|
||||||
strb.append(" ");
|
strb.append(" ");
|
||||||
strb.append(value);
|
strb.append(value);
|
||||||
@@ -402,13 +387,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InputStream getConfigInputStream() {
|
private InputStream getConfigInputStream() {
|
||||||
|
File dataDirectory = new File(torDirectory, ".tor");
|
||||||
StringBuilder strb = new StringBuilder();
|
StringBuilder strb = new StringBuilder();
|
||||||
append(strb, "ControlPort", torControlPort);
|
append(strb, "ControlPort", torControlPort);
|
||||||
append(strb, "CookieAuthentication", 1);
|
append(strb, "CookieAuthentication", 1);
|
||||||
|
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||||
append(strb, "DisableNetwork", 1);
|
append(strb, "DisableNetwork", 1);
|
||||||
append(strb, "RunAsDaemon", 1);
|
append(strb, "RunAsDaemon", 1);
|
||||||
append(strb, "SafeSocks", 1);
|
append(strb, "SafeSocks", 1);
|
||||||
append(strb, "SocksPort", torSocksPort);
|
append(strb, "SocksPort", torSocksPort);
|
||||||
|
strb.append("GeoIPFile\n");
|
||||||
|
strb.append("GeoIPv6File\n");
|
||||||
//noinspection CharsetObjectCanBeUsed
|
//noinspection CharsetObjectCanBeUsed
|
||||||
return new ByteArrayInputStream(
|
return new ByteArrayInputStream(
|
||||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||||
@@ -439,6 +428,23 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void waitForTorToStart(Process torProcess)
|
||||||
|
throws InterruptedException, PluginException {
|
||||||
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
|
// Log the first line of stdout (contains Tor and library versions)
|
||||||
|
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||||
|
// Read the process's stdout (and redirected stderr) until it detaches
|
||||||
|
while (stdout.hasNextLine()) stdout.nextLine();
|
||||||
|
stdout.close();
|
||||||
|
// Wait for the process to detach or exit
|
||||||
|
int exit = torProcess.waitFor();
|
||||||
|
if (exit != 0) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.warning("Tor exited with value " + exit);
|
||||||
|
throw new PluginException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void bind() {
|
private void bind() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
// If there's already a port number stored in config, reuse it
|
// If there's already a port number stored in config, reuse it
|
||||||
@@ -492,6 +498,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 +548,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 +593,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 +726,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 +740,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 +772,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 +787,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 +868,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 +880,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 +976,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 +1013,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 +1078,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
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user