Compare commits

..

49 Commits

Author SHA1 Message Date
akwizgran
66d907d1b2 Add comment explaining relationship between constants. 2022-06-13 11:26:07 +01:00
akwizgran
7d336c98e4 Raise stability threshold and poll less often when network is stable. 2022-06-13 11:26:07 +01:00
akwizgran
963e510c3b Fix PollerImplTest expectations. 2022-06-13 11:26:07 +01:00
akwizgran
213d2f1da4 Disable and re-enable network if we can't reach our own HS. 2022-06-13 11:26:07 +01:00
akwizgran
3038b92dbc Poll less frequently when own hidden service is stable. 2022-06-13 11:26:06 +01:00
akwizgran
7cd3c2890b Remove backoff for polling Tor plugin. 2022-06-13 11:26:06 +01:00
akwizgran
a38933df66 Read Tor process's stdout until it exits.
On Windows, RunAsDaemon is a no-op so we need to read stdout to find out when Tor has finished starting up, then continue to read and discard stdout until Tor exits.
2022-06-13 11:21:26 +01:00
akwizgran
4993873ae2 Add Tor and obfsproxy binaries for Windows. 2022-06-09 15:39:27 +01:00
akwizgran
02b805ce42 Disable GeoIPFile and GeoIPv6File options.
On Windows, Tor falls back to the default paths if these options aren't specified and then refuses to start because the default paths are relative.
2022-06-09 15:39:26 +01:00
akwizgran
1a6ba16a59 Add windowsJar task. 2022-06-09 15:39:26 +01:00
akwizgran
654a05df8a Use Windows Tor plugin in briar-headless. 2022-06-09 15:39:26 +01:00
akwizgran
ffe1876337 Redirect standard error (copied from Nico's branch). 2022-06-09 15:39:26 +01:00
akwizgran
98963955b1 Use default SecureRandomProvider on Windows. 2022-06-09 15:39:26 +01:00
akwizgran
d83efce002 Add WindowsTorPlugin and factory. 2022-06-09 15:39:26 +01:00
Torsten Grote
efb1b8c1ad Merge branch '2292-contact-mailbox-download-worker' into 'master'
Mailbox download worker for a contact's mailbox

Closes #2292

See merge request briar/briar!1658
2022-06-08 16:31:35 +00:00
akwizgran
3f36db8b3a Merge branch 'obfs4-bridges-for-dpi-countries' into 'master'
Use non-default obfs4 bridges alongside meek in countries with advanced firewalls

See merge request briar/briar!1663
2022-06-08 14:13:43 +00:00
akwizgran
a2f4e70a48 Remove a failing bridge. 2022-06-08 14:44:05 +01:00
akwizgran
01e72eff40 Always remove observers in destroy(). 2022-06-08 13:56:46 +01:00
Torsten Grote
dbcea3e1d1 Merge branch '1898-memory-stats' into 'master'
Pass memory stats from main process to crash reporter process

See merge request briar/briar!1662
2022-06-08 11:30:09 +00:00
akwizgran
6288577daa Add javadoc explaining worker's lifecycle. 2022-06-08 12:13:07 +01:00
akwizgran
5d363496bd Download files in the order the mailbox returns them. 2022-06-08 12:03:11 +01:00
akwizgran
75b5c92495 Pass memory stats from main process to crash reporter process. 2022-06-08 11:49:56 +01:00
Torsten Grote
bcc98cc4c9 Merge branch 'remove-bridge-test-from-release-pipeline' into 'master'
Remove BridgeTest from release pipeline

See merge request briar/briar!1661
2022-06-07 11:57:07 +00:00
Torsten Grote
2d605089bc Merge branch 'skip-hypersql-tests-if-crypto-strength-is-limited' into 'master'
Skip HyperSQL tests if the test environment has crypto restrictions

See merge request briar/briar!1660
2022-06-07 11:56:04 +00:00
Torsten Grote
01f8be1b66 Merge branch 'return-early-if-services-are-stopped-twice' into 'master'
Return early if LifecycleManager#stopServices() is called twice

See merge request briar/briar!1659
2022-06-07 11:55:07 +00:00
akwizgran
eac6d0aa40 Remove BridgeTest from release pipeline. 2022-06-07 12:46:03 +01:00
akwizgran
713be403eb Add some more non-default and vanilla bridges. 2022-06-07 12:18:59 +01:00
akwizgran
2fd948b81d Use non-default obfs4 bridges in countries that use DPI. 2022-06-07 12:18:24 +01:00
akwizgran
62af5e858c Merge branch 'Feedback_fix' into 'master'
Removed word limit on feedback.

See merge request briar/briar!1657
2022-06-07 10:59:45 +00:00
akwizgran
2201585a34 Skip HyperSQL tests if the test environment has crypto restrictions. 2022-06-07 11:11:41 +01:00
akwizgran
97d11cc602 Add tests for download worker. 2022-06-07 10:43:29 +01:00
akwizgran
79f41064e4 Add download worker for a contact's mailbox. 2022-06-07 10:43:29 +01:00
akwizgran
9aacd9d3d8 Allow observers to be removed. 2022-06-07 10:39:35 +01:00
FlyingP1g FlyingP1g
78f4dee43d Removed word limit on feedback. 2022-06-06 21:15:46 +03:00
akwizgran
2b4a1cf54b Refactor SimpleApiCall to support lambdas. 2022-06-06 17:40:19 +01:00
akwizgran
bb71de1a78 Merge branch '2319-mailbox-version-warning' into 'master'
Show warning if own mailbox's API version is incompatible

Closes #2319

See merge request briar/briar!1651
2022-06-06 16:23:15 +00:00
Torsten Grote
08bf13e44f Move check for common mailbox versions into a helper method
and use this in the UI for knowing which app needs to be updated.
2022-06-06 11:04:55 -03:00
Torsten Grote
cc7de2c70a Show warning if own mailbox's API version is incompatible 2022-06-06 11:00:05 -03:00
Torsten Grote
0f4aa8027a Include mailbox server versions in MailboxStatus
so we know if the mailbox is incompatible with Briar
2022-06-06 11:00:04 -03:00
Torsten Grote
b161a5e115 Merge branch '2292-mailbox-file-manager' into 'master'
Add mailbox plugin and file manager for downloads

See merge request briar/briar!1655
2022-06-06 11:51:22 +00:00
akwizgran
e112f69c4e Split onError() into two methods. 2022-06-04 13:00:05 +01:00
Torsten Grote
4623d03c93 Merge branch '2292-tor-reachability-monitor' into 'master'
Tor reachability monitor

See merge request briar/briar!1654
2022-06-03 17:08:14 +00:00
akwizgran
b128220be3 Add MailboxFileManager for downloads (uploads to be added later). 2022-06-03 17:55:19 +01:00
akwizgran
6aa24af94c Add ConnectionManager method for incoming mailbox connections. 2022-06-03 17:13:20 +01:00
akwizgran
de63a50662 Add mailbox plugin. 2022-06-03 17:13:20 +01:00
akwizgran
5517ac14ed Address review feedback. 2022-06-03 17:09:51 +01:00
akwizgran
2672d82a40 Add unit tests for TorReachabilityMonitorImpl. 2022-06-01 16:29:30 +01:00
akwizgran
63c0210047 Add Tor reachability monitor. 2022-05-31 16:24:59 +01:00
akwizgran
47085722da Return early if LifecycleManager#stopServices() is called twice.
This could happen if the app shuts down spontaneously (eg due to low memory) concurrently with a manual shutdown.
2021-11-17 15:38:44 +00:00
97 changed files with 2640 additions and 439 deletions

View File

@@ -118,11 +118,3 @@ mailbox integration test:
- cd "$CI_PROJECT_DIR"
- bramble-core/src/test/bash/wait-for-mailbox.sh
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
pre_release_tests:
extends: .optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
only:
- tags

View File

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

View File

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

View File

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

View File

@@ -8,8 +8,6 @@ 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.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
@@ -44,7 +42,6 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
@@ -56,7 +53,7 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
Application app,
AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
eventBus, torSocketFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app;
@@ -76,14 +73,14 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
}
@Override
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
TorPlugin createPluginInstance(TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture) {
return new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture,
torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}

View File

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

View File

@@ -1,5 +1,10 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -8,6 +13,32 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
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.

View File

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

View File

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

View File

@@ -2,10 +2,14 @@ package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
import static org.briarproject.bramble.api.mailbox.MailboxHelper.getHighestCommonMajorVersion;
@Immutable
@NotNullByDefault
@@ -13,12 +17,15 @@ public class MailboxStatus {
private final long lastAttempt, lastSuccess;
private final int attemptsSinceSuccess;
private final List<MailboxVersion> serverSupports;
public MailboxStatus(long lastAttempt, long lastSuccess,
int attemptsSinceSuccess) {
int attemptsSinceSuccess,
List<MailboxVersion> serverSupports) {
this.lastAttempt = lastAttempt;
this.lastSuccess = lastSuccess;
this.attemptsSinceSuccess = attemptsSinceSuccess;
this.serverSupports = serverSupports;
}
/**
@@ -67,4 +74,13 @@ public class MailboxStatus {
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
}
/**
* @return a positive integer if the mailbox is compatible. Same result as
* {@link MailboxHelper#getHighestCommonMajorVersion(List, List)}.
*/
public int getMailboxCompatibility() {
return getHighestCommonMajorVersion(CLIENT_SUPPORTS, serverSupports);
}
}

View File

@@ -1,7 +1,9 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.event.EventBus;
public interface BackoffFactory {
Backoff createBackoff(int minInterval, int maxInterval,
double base);
Backoff createBackoff(EventBus eventBus, TransportId transportId,
int minInterval, int maxInterval, double base);
}

View File

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

View File

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

View File

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

View File

@@ -40,6 +40,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -50,6 +51,7 @@ import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
@@ -335,4 +337,13 @@ public class TestUtils {
}
return false;
}
public static boolean isCryptoStrengthUnlimited() {
try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
== Integer.MAX_VALUE;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError();
}
}
}

View File

@@ -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];
read(in, tag);
return tag;

View File

@@ -67,7 +67,15 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionReader r) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
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

View File

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

View File

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

View File

@@ -22,10 +22,21 @@ interface ConnectivityChecker {
* the check succeeds. If a check is already running then the observer is
* called when the check succeeds. If a connectivity check has recently
* succeeded then the observer is called immediately.
* <p>
* Observers are removed after being called, or when the checker is
* {@link #destroy() destroyed}.
*/
void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o);
/**
* Removes an observer that was added via
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
* there are no remaining observers and a connectivity check is running
* then the check will be cancelled.
*/
void removeObserver(ConnectivityObserver o);
interface ConnectivityObserver {
void onConnectivityCheckSucceeded();
}

View File

@@ -80,8 +80,7 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
// The last connectivity check is stale, start a new one
connectivityObservers.add(o);
ApiCall task =
createConnectivityCheckTask(properties);
ApiCall task = createConnectivityCheckTask(properties);
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
} else {
// The last connectivity check is fresh
@@ -108,4 +107,16 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
o.onConnectivityCheckSucceeded();
}
}
@Override
public void removeObserver(ConnectivityObserver o) {
synchronized (lock) {
if (destroyed) return;
connectivityObservers.remove(o);
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
connectivityCheck.cancel();
connectivityCheck = null;
}
}
}
}

View File

@@ -5,8 +5,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@@ -24,16 +22,11 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall() {
@Override
void tryToCallApi() throws IOException, ApiException {
if (!mailboxApi.checkStatus(properties)) {
throw new ApiException();
}
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
}
};
return new SimpleApiCall(() -> {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
});
}
}

View File

@@ -0,0 +1,241 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class ContactMailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking the inbox, downloading and
* deleting any files, and checking again until the inbox is empty.
* <p>
* The worker then waits for our Tor hidden service to be reachable before
* starting its second download cycle. This ensures that if a contact
* tried and failed to connect to our hidden service before it was
* reachable, and therefore uploaded a file to the mailbox instead, we'll
* find the file in the second download cycle.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
private static final Logger LOG =
getLogger(ContactMailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
ContactMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.connectivityChecker = connectivityChecker;
this.torReachabilityMonitor = torReachabilityMonitor;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) throw new IllegalStateException();
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
torReachabilityMonitor.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.DOWNLOAD_CYCLE_1;
// Start first download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
}
private void apiCallListInbox() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing inbox");
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
if (files.isEmpty()) onDownloadCycleFinished();
else downloadNextFile(new LinkedList<>(files));
}
private void onDownloadCycleFinished() {
boolean addObserver = false;
synchronized (lock) {
if (state == State.DOWNLOAD_CYCLE_1) {
LOG.info("First download cycle finished");
state = State.WAITING_FOR_TOR;
addObserver = true;
} else if (state == State.DOWNLOAD_CYCLE_2) {
LOG.info("Second download cycle finished");
state = State.FINISHED;
}
}
if (addObserver) {
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
torReachabilityMonitor.addOneShotObserver(this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) torReachabilityMonitor.removeObserver(this);
}
}
private void downloadNextFile(Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
}
}
private void apiCallDownloadFile(MailboxFile file,
Queue<MailboxFile> queue) throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file.name, tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file.name);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next file
logException(LOG, INFO, e);
}
if (queue.isEmpty()) {
// List the inbox again to check for files that may have arrived
// while we were downloading
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
} else {
downloadNextFile(queue);
}
}
@Override
public void onTorReachable() {
LOG.info("Our Tor hidden service is reachable");
synchronized (lock) {
if (state != State.WAITING_FOR_TOR) return;
state = State.DOWNLOAD_CYCLE_2;
// Start second download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
}
}

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -19,18 +18,9 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonList;
@NotNullByDefault
interface MailboxApi {
/**
* Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}.
*/
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
new MailboxVersion(1, 0));
List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException;

View File

@@ -24,6 +24,8 @@ interface MailboxApiCaller {
* 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.

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxFileManager {
/**
* Creates an empty file for storing a download.
*/
File createTempFileForDownload() throws IOException;
/**
* Handles a file that has been downloaded. The file should be created
* with {@link #createTempFileForDownload()}.
*/
void handleDownloadedFile(File f);
}

View File

@@ -0,0 +1,174 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
private static final Logger LOG =
getLogger(MailboxFileManagerImpl.class.getName());
// Package access for testing
static final String DOWNLOAD_DIR_NAME = "downloads";
private final Executor ioExecutor;
private final PluginManager pluginManager;
private final ConnectionManager connectionManager;
private final LifecycleManager lifecycleManager;
private final File mailboxDir;
private final EventBus eventBus;
private final CountDownLatch orphanLatch = new CountDownLatch(1);
@Inject
MailboxFileManagerImpl(@IoExecutor Executor ioExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
LifecycleManager lifecycleManager,
@MailboxDirectory File mailboxDir,
EventBus eventBus) {
this.ioExecutor = ioExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.lifecycleManager = lifecycleManager;
this.mailboxDir = mailboxDir;
this.eventBus = eventBus;
}
@Override
public File createTempFileForDownload() throws IOException {
// Wait for orphaned files to be handled before creating new files
try {
orphanLatch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", downloadDir);
}
private File createDirectoryIfNeeded(String name) throws IOException {
File dir = new File(mailboxDir, name);
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
if (!dir.isDirectory()) {
throw new IOException("Failed to create directory '" + name + "'");
}
return dir;
}
@Override
public void handleDownloadedFile(File f) {
// We shouldn't reach this point until the plugin has been started
SimplexPlugin plugin =
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, f.getAbsolutePath());
TransportConnectionReader reader = plugin.createReader(p);
if (reader == null) {
LOG.warning("Failed to create reader for downloaded file");
return;
}
TransportConnectionReader decorated = new MailboxFileReader(reader, f);
LOG.info("Reading downloaded file");
connectionManager.manageIncomingConnection(ID, decorated,
exception -> isHandlingComplete(exception, true));
}
private boolean isHandlingComplete(boolean exception, boolean recognised) {
// If we've successfully read the file then we're done
if (!exception && recognised) return true;
// If the app is shutting down we may get spurious IO exceptions
// due to executors being shut down. Leave the file in the download
// directory and we'll try to read it again at the next startup
return !lifecycleManager.getLifecycleState().isAfter(RUNNING);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) {
ioExecutor.execute(this::handleOrphanedFiles);
eventBus.removeListener(this);
}
}
}
/**
* This method is called at startup, as soon as the plugin is started, to
* handle any files that were left in the download directory at the last
* shutdown.
*/
@IoExecutor
private void handleOrphanedFiles() {
try {
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
File[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphans, new files can be created
orphanLatch.countDown();
if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
private class MailboxFileReader implements TransportConnectionReader {
private final TransportConnectionReader delegate;
private final File file;
private MailboxFileReader(TransportConnectionReader delegate,
File file) {
this.delegate = delegate;
this.file = file;
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public void dispose(boolean exception, boolean recognised)
throws IOException {
delegate.dispose(exception, recognised);
if (isHandlingComplete(exception, recognised)) {
LOG.info("Deleting downloaded file");
if (!file.delete()) {
LOG.warning("Failed to delete downloaded file");
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
@@ -21,10 +22,10 @@ import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
@Module
public class MailboxModule {
@@ -34,6 +35,8 @@ public class MailboxModule {
MailboxUpdateValidator mailboxUpdateValidator;
@Inject
MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxFileManager mailboxFileManager;
}
@Provides
@@ -101,4 +104,14 @@ public class MailboxModule {
}
return mailboxUpdateManager;
}
@Provides
@Singleton
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
if (featureFlags.shouldEnableMailbox()) {
eventBus.addListener(mailboxFileManager);
}
return mailboxFileManager;
}
}

View File

@@ -23,6 +23,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@@ -60,15 +61,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
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]));
}
List<MailboxVersion> serverSupports = parseServerSupports(s);
try {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, serverSupports);
@@ -115,18 +108,28 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
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
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
Settings s = new Settings();
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
MailboxStatus status = new MailboxStatus(now, now, 0);
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@@ -141,7 +144,9 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
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));
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
}
@@ -165,4 +170,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
if (isNullOrEmpty(filename)) return null;
return filename;
}
private List<MailboxVersion> parseServerSupports(Settings s)
throws DbException {
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
// We know we were paired, so we must have proper serverSupports
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
throw new DbException();
}
List<MailboxVersion> serverSupports = new ArrayList<>();
for (int i = 0; i < ints.length - 1; i += 2) {
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
}
return serverSupports;
}
}

View File

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

View File

@@ -16,17 +16,20 @@ import static org.briarproject.bramble.util.LogUtils.logException;
* Convenience class for making simple API calls that don't return values.
*/
@NotNullByDefault
public abstract class SimpleApiCall implements ApiCall {
class SimpleApiCall implements ApiCall {
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
abstract void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
private final Attempt attempt;
SimpleApiCall(Attempt attempt) {
this.attempt = attempt;
}
@Override
public boolean callApi() {
try {
tryToCallApi();
attempt.tryToCallApi();
return false; // Succeeded, don't retry
} catch (IOException | ApiException e) {
logException(LOG, WARNING, e);
@@ -36,4 +39,17 @@ public abstract class SimpleApiCall implements ApiCall {
return false; // Failed tolerably, don't retry
}
}
interface Attempt {
/**
* Makes a single attempt to call an API endpoint. If this method
* throws an {@link IOException} or an {@link ApiException}, the call
* will be retried. If it throws a {@link TolerableFailureException}
* or returns without throwing an exception, the call will not be
* retried.
*/
void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
@@ -26,6 +25,7 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -67,6 +67,7 @@ import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
@@ -107,7 +108,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault
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 = {
"CIRC",
@@ -124,14 +125,53 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final Executor ioExecutor, wakefulIoExecutor;
/**
* After this many consecutive successful connections to our own hidden
* service we consider the network to be stable.
* <p>
* This constant times {@link #POLLING_INTERVAL_UNSTABLE} should be
* greater than {@link #POLLING_INTERVAL_STABLE}. This ensures that if
* we experience a network outage that isn't detected by the
* {@link NetworkManager}, and if one of our contacts comes online during
* the outage, then either the outage lasts longer than
* {@link #POLLING_INTERVAL_STABLE}, in which case we detect the outage
* by failing to connect to our own hidden service, or the outage ends
* before the contact considers their own network connection to be stable,
* in which case the contact is still trying to connect to us when the
* outage ends. Either way, we don't end up in a situation where both we
* and the contact consider our network connections to be stable and stop
* trying to connect to each other, despite both being online.
*/
private static final int STABLE_NETWORK_THRESHOLD = 10;
/**
* After this many consecutive failed connections to our own hidden service
* we consider our connection to the Tor network to be broken.
*/
private static final int BROKEN_NETWORK_THRESHOLD = 3;
/**
* How often to poll our own hidden service when the network is considered
* to be stable.
*/
private static final int POLLING_INTERVAL_STABLE =
(int) MINUTES.toMillis(10);
/**
* How often to poll our own hidden service and our contacts' hidden
* services when the network is considered to be unstable.
*/
private static final int POLLING_INTERVAL_UNSTABLE =
(int) MINUTES.toMillis(2);
protected final Executor ioExecutor;
private final Executor wakefulIoExecutor;
private final Executor connectionStatusExecutor;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final Clock clock;
private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback;
private final String architecture;
@@ -151,6 +191,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
private volatile String ownOnion = null;
protected abstract int getProcessId();
@@ -165,7 +206,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -183,7 +223,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto;
this.callback = callback;
this.architecture = architecture;
@@ -254,34 +293,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
LOG.info(stderr.nextLine());
}
}
stdout.close();
stderr.close();
}
try {
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
// Wait for the Tor process to start
waitForTorToStart(torProcess);
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
@@ -397,7 +417,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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(" ");
strb.append(value);
@@ -405,13 +425,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -442,6 +466,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() {
ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it
@@ -471,7 +512,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor
acceptContactConnections(ss);
});
@@ -510,6 +550,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
String onion3 = response.get(HS_ADDRESS);
ownOnion = onion3;
if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(onion3));
}
@@ -538,7 +579,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
LOG.info("Connection received");
backoff.reset();
callback.handleConnection(new TorTransportConnection(this, s));
}
}
@@ -615,14 +655,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
if (state.isNetworkStable()) {
LOG.info("Using stable polling interval");
return POLLING_INTERVAL_STABLE;
} else {
LOG.info("Using unstable polling interval");
return POLLING_INTERVAL_UNSTABLE;
}
}
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
String ownOnion = this.ownOnion;
if (ownOnion == null) {
// Our own hidden service hasn't been created yet
pollContacts(properties);
} else {
// If the network is unstable, poll our contacts
boolean stable = state.isNetworkStable();
if (!stable) pollContacts(properties);
// Poll our own hidden service to check if the network is stable
wakefulIoExecutor.execute(() -> {
LOG.info("Connecting to own hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, ownOnion);
DuplexTransportConnection d = createConnection(p);
if (d == null) {
LOG.info("Could not connect to own hidden service");
state.onStabilityCheckFailed();
// If the network was previously considered stable then
// we didn't poll our contacts above, so poll them now
if (stable) pollContacts(properties);
} else {
LOG.info("Connected to own hidden service");
// Close the connection (this will cause the other end of
// the connection to log an EOFException)
try {
d.getWriter().dispose(false);
d.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
state.onStabilityCheckSucceeded();
}
});
}
}
private void pollContacts(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
if (properties.isEmpty() || getState() != ACTIVE) return;
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
}
@@ -631,10 +715,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connect(TransportProperties p, ConnectionHandler h) {
wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
h.handleConnection(d);
}
if (d != null) h.handleConnection(d);
});
}
@@ -757,7 +838,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@@ -784,6 +864,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (msg.startsWith("Switching to guard context")) {
state.resetNetworkStability();
}
}
@Override
@@ -810,12 +893,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped");
state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (!state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
if (!state.getAndSetCircuitBuilt(true)) LOG.info("Circuit built");
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built");
@@ -860,7 +939,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
if (c.getTransportId().equals(getId())
&& !c.isIncoming() && c.isException()) {
LOG.info("Outgoing connection closed with exception");
// The failure may indicate that the network is unstable
state.resetNetworkStability();
}
} else if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
@@ -877,6 +964,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void disableAndReenableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) {
logException(LOG, WARNING, ex);
}
});
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
@@ -1005,6 +1104,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this")
private int reasonsDisabled = 0;
/**
* The number of consecutive successful (positive) or failed (negative)
* connections to our own hidden service.
*/
@GuardedBy("this")
private int networkStability = 0;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
@@ -1099,6 +1205,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
resetNetworkStability();
callback.pluginStateChanged(getState());
}
}
@@ -1109,5 +1216,43 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
private synchronized boolean isNetworkStable() {
return networkStability >= STABLE_NETWORK_THRESHOLD;
}
private synchronized void onStabilityCheckSucceeded() {
if (networkStability <= 0) networkStability = 1;
else networkStability++;
logNetworkStability();
}
private synchronized void onStabilityCheckFailed() {
if (networkStability >= 0) networkStability = -1;
else networkStability--;
if (networkStability <= -BROKEN_NETWORK_THRESHOLD) {
LOG.warning("Connection to Tor appears to be broken");
resetNetworkStability();
disableAndReenableNetwork();
} else {
logNetworkStability();
}
}
private synchronized void resetNetworkStability() {
int old = networkStability;
networkStability = 0;
logNetworkStability();
if (old >= STABLE_NETWORK_THRESHOLD) {
callback.pollingIntervalDecreased();
}
}
@GuardedBy("this")
private void logNetworkStability() {
if (LOG.isLoggable(INFO)) {
LOG.info("Network stability score " + networkStability);
}
}
}
}

View File

@@ -6,8 +6,6 @@ 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;
@@ -41,16 +39,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
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;
@@ -66,7 +60,6 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
@@ -81,7 +74,6 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
@@ -95,7 +87,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@Nullable
abstract String getArchitectureForTorBinary();
abstract TorPlugin createPluginInstance(Backoff backoff,
abstract TorPlugin createPluginInstance(
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture);
@@ -122,11 +114,9 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
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,
TorPlugin plugin = createPluginInstance(torRendezvousCrypto,
callback, architecture);
eventBus.addListener(plugin);
return plugin;

View File

@@ -15,7 +15,6 @@ n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cer
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
@@ -24,7 +23,11 @@ n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0
n Bridge obfs4 70.34.213.156:12345 BC1C79ABBAE085D305346E7A2B0E838953B4B4D3 cert=3Sk4uA3/NiAsn4ObOUzjIzARclGmkiEUrku8o8bkq4ZL+dek9uLj/d5LZ5nAXT6L9S0CZA iat-mode=0
n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Before;
import java.io.File;
import java.sql.Connection;
@@ -10,10 +11,18 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
import static org.junit.Assume.assumeTrue;
public class BasicHyperSqlTest extends BasicDatabaseTest {
private final SecretKey key = TestUtils.getSecretKey();
@Before
public void setUp() {
assumeTrue(isCryptoStrengthUnlimited());
}
@Override
protected String getBinaryType() {
return "BINARY(32)";

View File

@@ -3,9 +3,18 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
import org.junit.Before;
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
import static org.junit.Assume.assumeTrue;
public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
@Before
public void setUp() {
assumeTrue(isCryptoStrengthUnlimited());
}
@Override
protected JdbcDatabase createDatabase(DatabaseConfig config,
MessageFactory messageFactory, Clock clock) {

View File

@@ -1,13 +1,22 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.junit.Before;
import java.sql.Connection;
import java.util.List;
import static org.briarproject.bramble.test.TestUtils.isCryptoStrengthUnlimited;
import static org.junit.Assume.assumeTrue;
@NotNullByDefault
public class HyperSqlMigrationTest extends DatabaseMigrationTest {
@Before
public void setUp() {
assumeTrue(isCryptoStrengthUnlimited());
}
@Override
Database<Connection> createDatabase(
List<Migration<Connection>> migrations) {

View File

@@ -9,12 +9,16 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
@@ -57,6 +61,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
lifecycleManager.registerOpenDatabaseHook(hook);
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
assertTrue(called.get());
}
@@ -69,6 +74,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
}});
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
assertEquals(STARTING, lifecycleManager.getLifecycleState());
}
@Test
@@ -80,5 +86,40 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
}});
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
assertEquals(STARTING, lifecycleManager.getLifecycleState());
}
@Test
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
oneOf(db).close();
}});
lifecycleManager.stopServices();
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
// Calling stopServices() again should not broadcast another event or
// try to close the DB again
lifecycleManager.stopServices();
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
}
}

View File

@@ -10,8 +10,8 @@ import org.junit.Test;
import javax.annotation.Nonnull;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.mailbox.ConnectivityCheckerImpl.CONNECTIVITY_CHECK_FRESHNESS_MS;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
@@ -187,6 +187,32 @@ public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
checker.onConnectivityCheckSucceeded(now);
}
@Test
public void testCheckIsCancelledWhenObserverIsRemoved() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the observer is removed the check should be cancelled
context.checking(new Expectations() {{
oneOf(task).cancel();
}});
checker.removeObserver(observer1);
// If the check runs anyway (cancellation came too late) the observer
// should not be called
checker.onConnectivityCheckSucceeded(now);
}
private ConnectivityCheckerImpl createChecker() {
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {

View File

@@ -13,7 +13,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

View File

@@ -0,0 +1,215 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertFalse;
public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor torReachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final MailboxFile file1 =
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
private final MailboxFile file2 =
new MailboxFile(new MailboxFileId(getRandomId()), now);
private final List<MailboxFile> files = asList(file1, file2);
private File testDir, tempFile;
private ContactMailboxDownloadWorker worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
worker = new ContactMailboxDownloadWorker(connectivityChecker,
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties);
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start();
// When the worker is destroyed it should remove the connectivity
// and reachability observers
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy();
}
@Test
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start();
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds some files to download,
// it should start a download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(files));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
}});
assertFalse(listTask.get().callApi());
// When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file1.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
}});
assertFalse(downloadTask.get().callApi());
// When the first delete task runs it should delete the file, ignore
// the tolerable failure, and start a download task for the next file
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file1.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
}});
assertFalse(deleteTask.get().callApi());
// When the second download task runs it should download the file to
// the location provided by the file manager and start a delete task
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file2.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
}});
assertFalse(downloadTask.get().callApi());
// When the second delete task runs it should delete the file and
// start a list-inbox task to check for files that may have arrived
// since the first download cycle started
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file2.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
assertFalse(deleteTask.get().callApi());
// When the list-inbox tasks runs and finds no more files to download,
// it should add a Tor reachability observer
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
}});
assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
worker.onTorReachable();
// When the list-inbox tasks runs and finds no more files to download,
// it should finish the second download cycle
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
}});
assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy();
}
}

View File

@@ -35,7 +35,7 @@ import okio.Buffer;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;

View File

@@ -0,0 +1,194 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final PluginManager pluginManager =
context.mock(PluginManager.class);
private final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
private final LifecycleManager lifecycleManager =
context.mock(LifecycleManager.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
private final TransportConnectionReader transportConnectionReader =
context.mock(TransportConnectionReader.class);
private File mailboxDir;
private MailboxFileManagerImpl manager;
@Before
public void setUp() {
mailboxDir = getTestDirectory();
manager = new MailboxFileManagerImpl(ioExecutor, pluginManager,
connectionManager, lifecycleManager, mailboxDir, eventBus);
}
@After
public void tearDown() {
deleteTestDirectory(mailboxDir);
}
@Test
public void testHandlesOrphanedFilesAtStartup() throws Exception {
// Create an orphaned file, left behind at the previous shutdown
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
downloadDir.mkdirs();
File orphan = new File(downloadDir, "orphan");
assertTrue(orphan.createNewFile());
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, orphan.getAbsolutePath());
// When the plugin becomes active the orphaned file should be handled
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(eventBus).removeListener(manager);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createReader(props);
will(returnValue(transportConnectionReader));
oneOf(connectionManager).manageIncomingConnection(with(ID),
with(any(TransportConnectionReader.class)),
with(any(TagController.class)));
}});
manager.eventOccurred(new TransportActiveEvent(ID));
}
@Test
public void testDeletesFileWhenReadSucceeds() throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
File f = manager.createTempFileForDownload();
AtomicReference<TransportConnectionReader> reader =
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
// The read is successful, so the tag controller should allow the tag
// to be marked as read and the reader should delete the file
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(false, true);
}});
assertTrue(controller.get().shouldMarkTagAsRecognised(false));
reader.get().dispose(false, true);
assertFalse(f.exists());
}
@Test
public void testDeletesFileWhenTagIsNotRecognised() throws Exception {
testDeletesFile(false, RUNNING, false);
}
@Test
public void testDeletesFileWhenReadFails() throws Exception {
testDeletesFile(true, RUNNING, false);
}
@Test
public void testDoesNotDeleteFileWhenTagIsNotRecognisedAtShutdown()
throws Exception {
testDeletesFile(false, STOPPING, true);
}
@Test
public void testDoesNotDeleteFileWhenReadFailsAtShutdown()
throws Exception {
testDeletesFile(true, STOPPING, true);
}
private void testDeletesFile(boolean recognised, LifecycleState state,
boolean fileExists) throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
File f = manager.createTempFileForDownload();
AtomicReference<TransportConnectionReader> reader =
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(true, recognised);
oneOf(lifecycleManager).getLifecycleState();
will(returnValue(state));
}});
reader.get().dispose(true, recognised);
assertEquals(fileExists, f.exists());
}
private void expectCheckForOrphans() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(eventBus).removeListener(manager);
}});
}
private void expectPassFileToConnectionManager(File f,
AtomicReference<TransportConnectionReader> reader,
AtomicReference<TagController> controller) {
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, f.getAbsolutePath());
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createReader(props);
will(returnValue(transportConnectionReader));
oneOf(connectionManager).manageIncomingConnection(with(ID),
with(any(TransportConnectionReader.class)),
with(any(TagController.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(reader,
TransportConnectionReader.class, 1),
new CaptureArgumentAction<>(controller,
TagController.class, 2)
));
}});
}
}

View File

@@ -146,12 +146,17 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
@Test
public void testRecordsSuccess() throws Exception {
Transaction txn = new Transaction(null, false);
Settings oldSettings = new Settings();
oldSettings
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(oldSettings));
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});

View File

@@ -17,7 +17,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

View File

@@ -0,0 +1,246 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.event.EventBus;
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 org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.mailbox.TorReachabilityMonitor.REACHABILITY_PERIOD_MS;
public class TorReachabilityMonitorImplTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final TaskScheduler taskScheduler =
context.mock(TaskScheduler.class);
private final PluginManager pluginManager =
context.mock(PluginManager.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final Plugin plugin = context.mock(Plugin.class);
private final Cancellable scheduledTask = context.mock(Cancellable.class);
private final TorReachabilityObserver observer =
context.mock(TorReachabilityObserver.class);
private TorReachabilityMonitorImpl monitor;
@Before
public void setUp() {
monitor = new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
pluginManager, eventBus);
}
@Test
public void testSchedulesTaskWhenStartedIfTorIsActive() {
// Starting the monitor should schedule a task
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(ACTIVE));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(returnValue(scheduledTask));
}});
monitor.start();
// If Tor has only just become active and the TransportActiveEvent
// arrives after the task has already been scheduled, a second task
// should not be scheduled
monitor.eventOccurred(new TransportActiveEvent(ID));
// Destroying the monitor should cancel the task
context.checking(new Expectations() {{
oneOf(eventBus).removeListener(monitor);
oneOf(scheduledTask).cancel();
}});
monitor.destroy();
}
@Test
public void testSchedulesTaskWhenTorBecomesActive() {
// Starting the monitor should not schedule a task as Tor is inactive
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(ENABLING));
}});
monitor.start();
// When Tor becomes active, a task should be scheduled
context.checking(new Expectations() {{
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(returnValue(scheduledTask));
}});
monitor.eventOccurred(new TransportActiveEvent(ID));
// Destroying the monitor should cancel the task
context.checking(new Expectations() {{
oneOf(eventBus).removeListener(monitor);
oneOf(scheduledTask).cancel();
}});
monitor.destroy();
}
@Test
public void testCancelsTaskWhenTorBecomesInactive() {
// Starting the monitor should schedule a task
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(ACTIVE));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(returnValue(scheduledTask));
}});
monitor.start();
// When Tor becomes inactive, the task should be cancelled
context.checking(new Expectations() {{
oneOf(scheduledTask).cancel();
}});
monitor.eventOccurred(new TransportInactiveEvent(ID));
// Destroying the monitor should not affect the task, which has
// already been cancelled
context.checking(new Expectations() {{
oneOf(eventBus).removeListener(monitor);
}});
monitor.destroy();
}
@Test
public void testObserverRegisteredBeforeTorBecomesActiveIsCalled() {
// Starting the monitor should not schedule a task as Tor is inactive
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(DISABLED));
}});
monitor.start();
// Register an observer
monitor.addOneShotObserver(observer);
// When Tor becomes active, a task should be scheduled
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
returnValue(scheduledTask)
));
}});
monitor.eventOccurred(new TransportActiveEvent(ID));
// When the task runs, the observer should be called
context.checking(new Expectations() {{
oneOf(observer).onTorReachable();
}});
runnable.get().run();
}
@Test
public void testObserverRegisteredBeforeTorBecomesReachableIsCalled() {
// Starting the monitor should schedule a task
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(ACTIVE));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
returnValue(scheduledTask)
));
}});
monitor.start();
// Register an observer
monitor.addOneShotObserver(observer);
// When the task runs, the observer should be called
context.checking(new Expectations() {{
oneOf(observer).onTorReachable();
}});
runnable.get().run();
}
@Test
public void testObserverRegisteredAfterTorBecomesReachableIsCalled() {
// Starting the monitor should schedule a task
AtomicReference<Runnable> runnable = new AtomicReference<>(null);
context.checking(new Expectations() {{
oneOf(eventBus).addListener(monitor);
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(ACTIVE));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(REACHABILITY_PERIOD_MS),
with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(runnable, Runnable.class, 0),
returnValue(scheduledTask)
));
}});
monitor.start();
// When the task runs, no observers have been registered yet
runnable.get().run();
// When an observer is registered, it should be called immediately
context.checking(new Expectations() {{
oneOf(observer).onTorReachable();
}});
monitor.addOneShotObserver(observer);
}
}

View File

@@ -1,45 +1,69 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BackoffImplTest extends BrambleTestCase {
public class BackoffImplTest extends BrambleMockTestCase {
private final EventBus eventBus = context.mock(EventBus.class);
private final TransportId transportId = getTransportId();
private static final int MIN_INTERVAL = 60 * 1000;
private static final int MAX_INTERVAL = 60 * 60 * 1000;
private static final double BASE = 1.2;
@Test
public void testPollingIntervalStartsAtMinimum() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testIncrementIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
public void testIncrementMethodIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
}
@Test
public void testResetResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
public void testResetMethodResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(
PollingIntervalDecreasedEvent.class)));
}});
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
context.assertIsSatisfied();
// Resetting again should not broadcast another event
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testBaseAffectsBackoffSpeed() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
int interval = b.getPollingInterval();
BackoffImpl b1 = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE * 2);
BackoffImpl b1 = new BackoffImpl(eventBus, transportId, MIN_INTERVAL,
MAX_INTERVAL, BASE * 2);
b1.increment();
int interval1 = b1.getPollingInterval();
assertTrue(interval < interval1);
@@ -47,15 +71,16 @@ public class BackoffImplTest extends BrambleTestCase {
@Test
public void testIntervalDoesNotExceedMaxInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
for (int i = 0; i < 100; i++) b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
@Test
public void testIntervalDoesNotExceedMaxIntervalWithInfiniteMultiplier() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL,
Double.POSITIVE_INFINITY);
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, Double.POSITIVE_INFINITY);
b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
@@ -35,7 +35,6 @@ import java.util.concurrent.Executor;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.CollectionMatcher.collectionOf;
@@ -217,7 +216,7 @@ public class PollerImplTest extends BrambleMockTestCase {
}
@Test
public void testRescheduleOnConnectionOpened() {
public void testRescheduleOnPollingIntervalDecreased() {
Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{
@@ -240,8 +239,7 @@ public class PollerImplTest extends BrambleMockTestCase {
will(returnValue(cancellable));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
}
@Test
@@ -281,10 +279,8 @@ public class PollerImplTest extends BrambleMockTestCase {
will(returnValue(now + 1));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
}
@Test
@@ -328,10 +324,8 @@ public class PollerImplTest extends BrambleMockTestCase {
with(MILLISECONDS));
}});
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
}
@Test
@@ -378,48 +372,6 @@ public class PollerImplTest extends BrambleMockTestCase {
poller.eventOccurred(new TransportActiveEvent(transportId));
}
@Test
public void testDoesNotPollIfAllContactsAreConnected() throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule a polling task immediately
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(0L), with(MILLISECONDS));
will(returnValue(cancellable));
will(new RunAction());
// Running the polling task schedules the next polling task
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(random).nextDouble();
will(returnValue(0.5));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with((long) (pollingInterval * 0.5)),
with(MILLISECONDS));
will(returnValue(cancellable));
// Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties)));
oneOf(connectionRegistry).getConnectedOrBetterContacts(transportId);
will(returnValue(singletonList(contactId)));
// All contacts are connected, so don't poll the plugin
}});
poller.eventOccurred(new TransportActiveEvent(transportId));
}
@Test
public void testCancelsPollingOnTransportDeactivated() {
Plugin plugin = context.mock(Plugin.class);

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestFeatureFlagModule;
import org.briarproject.bramble.test.TestMailboxDirectoryModule;
import org.briarproject.bramble.test.TestSecureRandomModule;
import javax.inject.Singleton;
@@ -27,6 +28,7 @@ import dagger.Component;
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestFeatureFlagModule.class,
TestMailboxDirectoryModule.class,
RemovableDriveIntegrationTestModule.class,
RemovableDriveModule.class,
TestSecureRandomModule.class,

View File

@@ -342,6 +342,10 @@ public class LanTcpPluginTest extends BrambleTestCase {
public void pluginStateChanged(State newState) {
}
@Override
public void pollingIntervalDecreased() {
}
@Override
public void handleConnection(DuplexTransportConnection d) {
connectionsLatch.countDown();

View File

@@ -7,7 +7,6 @@ import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
@@ -15,7 +14,7 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -32,18 +31,18 @@ public class CircumventionProviderTest extends BrambleTestCase {
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
Set<String> nonDefaultBridges =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
// BRIDGES should be a subset of BLOCKED
assertTrue(blocked.containsAll(bridges));
// BRIDGES should be the union of the bridge type sets
Set<String> union = new HashSet<>(defaultBridges);
union.addAll(nonDefaultBridges);
union.addAll(meekBridges);
union.addAll(dpiBridges);
assertEquals(bridges, union);
// The bridge type sets should not overlap
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
assertEmptyIntersection(defaultBridges, meekBridges);
assertEmptyIntersection(nonDefaultBridges, meekBridges);
assertEmptyIntersection(defaultBridges, dpiBridges);
assertEmptyIntersection(nonDefaultBridges, dpiBridges);
}
@Test
@@ -56,8 +55,8 @@ public class CircumventionProviderTest extends BrambleTestCase {
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes(country));
}
for (String country : MEEK_BRIDGES) {
assertEquals(singletonList(MEEK),
for (String country : DPI_BRIDGES) {
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
provider.getSuitableBridgeTypes(country));
}
assertEquals(asList(DEFAULT_OBFS4, VANILLA),

View File

@@ -13,6 +13,7 @@ import dagger.Module;
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestFeatureFlagModule.class,
TestMailboxDirectoryModule.class,
TestPluginConfigModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import java.io.File;
import dagger.Module;
import dagger.Provides;
@Module
public class TestMailboxDirectoryModule {
@Provides
@MailboxDirectory
File provideMailboxDirectory() {
return new File("mailbox");
}
}

View File

@@ -18,7 +18,9 @@ dependencies {
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor-linux:$tor_version"
tor "org.briarproject:tor-windows:$tor_version"
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -69,8 +69,8 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
BluetoothConnectionFactory<StreamConnection> connectionFactory =
new JavaBluetoothConnectionFactory(connectionLimiter,
timeoutMonitor);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
connectionFactory, ioExecutor, wakefulIoExecutor, secureRandom,
backoff, callback, MAX_LATENCY, MAX_IDLE_TIME);

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
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.PluginCallback;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
@@ -29,7 +28,6 @@ abstract class JavaTorPlugin extends TorPlugin {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -40,7 +38,7 @@ abstract class JavaTorPlugin extends TorPlugin {
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
circumventionProvider, batteryManager,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);

View File

@@ -6,7 +6,6 @@ import com.sun.jna.Native;
import org.briarproject.bramble.api.battery.BatteryManager;
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.PluginCallback;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
@@ -29,7 +28,6 @@ class UnixTorPlugin extends JavaTorPlugin {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -40,7 +38,7 @@ class UnixTorPlugin extends JavaTorPlugin {
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
circumventionProvider, batteryManager,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);

View File

@@ -6,8 +6,6 @@ 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.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
@@ -39,7 +37,6 @@ public class UnixTorPluginFactory extends TorPluginFactory {
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
@@ -49,7 +46,7 @@ public class UnixTorPluginFactory extends TorPluginFactory {
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
eventBus, torSocketFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@@ -69,13 +66,13 @@ public class UnixTorPluginFactory extends TorPluginFactory {
}
@Override
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
TorPlugin createPluginInstance(TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture) {
return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}

View File

@@ -0,0 +1,95 @@
package org.briarproject.bramble.plugin.tor;
import com.sun.jna.platform.win32.Kernel32;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
@NotNullByDefault
class WindowsTorPlugin extends JavaTorPlugin {
WindowsTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);
}
@Override
protected int getProcessId() {
return Kernel32.INSTANCE.GetCurrentProcessId();
}
@Override
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
// Wait for the control port to be opened, then continue to read its
// stdout and stderr in a background thread until it exits.
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
ioExecutor.execute(() -> {
boolean started = false;
// Read the process's stdout (and redirected stderr)
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Startup has succeeded when the control port is open
while (stdout.hasNextLine()) {
String line = stdout.nextLine();
if (!started && line.contains("Opened Control listener")) {
success.add(true);
started = true;
}
}
stdout.close();
// If the control port wasn't opened, startup has failed
if (!started) success.add(false);
// Wait for the process to exit
try {
int exit = torProcess.waitFor();
if (LOG.isLoggable(INFO))
LOG.info("Tor exited with value " + exit);
} catch (InterruptedException e1) {
LOG.warning("Interrupted while waiting for Tor to exit");
Thread.currentThread().interrupt();
}
});
// Wait for the startup result
if (!success.take()) throw new PluginException();
}
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
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 javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Immutable
@NotNullByDefault
public class WindowsTorPluginFactory extends TorPluginFactory {
@Inject
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@Nullable
@Override
String getArchitectureForTorBinary() {
if (!isWindows()) return null;
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) return "windows-x86_64";
return null;
}
@Override
TorPlugin createPluginInstance(TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture) {
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}
}

View File

@@ -16,9 +16,7 @@ public class DesktopSecureRandomModule {
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac())
return new UnixSecureRandomProvider();
// TODO: Create a secure random provider for Windows
throw new UnsupportedOperationException();
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
return () -> null; // Use system default
}
}

View File

@@ -8,7 +8,6 @@ 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.BackoffFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
@@ -110,8 +109,6 @@ public class BridgeTest extends BrambleTestCase {
@Inject
EventBus eventBus;
@Inject
BackoffFactory backoffFactory;
@Inject
Clock clock;
@Inject
CryptoComponent crypto;
@@ -166,9 +163,8 @@ public class BridgeTest extends BrambleTestCase {
};
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, bridgeProvider,
batteryManager, clock, crypto, torDir,
SOCKS_PORT, CONTROL_PORT);
resourceProvider, bridgeProvider, batteryManager, clock,
crypto, torDir, SOCKS_PORT, CONTROL_PORT);
}
@After

View File

@@ -43,6 +43,10 @@ public class TestPluginCallback implements PluginCallback {
public void pluginStateChanged(State state) {
}
@Override
public void pollingIntervalDecreased() {
}
@Override
public void handleConnection(DuplexTransportConnection c) {
}

View File

@@ -25,7 +25,9 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
'org.briarproject:obfs4proxy-windows:0.0.12:obfs4proxy-windows-0.0.12.jar:392aa4b9d9c6fef0c659c4068d019d6c6471991bbb62ff00553884ec36018c7b',
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
'org.briarproject:tor-windows:0.4.5.12-2:tor-windows-0.4.5.12-2.jar:46599a15d099ed35a360113293f66acc119571c24ec2e37e85e4fb54b4722e07',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus;
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.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
@@ -27,6 +28,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
import org.briarproject.bramble.plugin.file.MailboxPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
@@ -63,6 +65,7 @@ import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -76,7 +79,6 @@ import dagger.Provides;
import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
@@ -156,6 +158,13 @@ public class AppModule {
return new AndroidDatabaseConfig(dbDir, keyDir, keyStrengthener);
}
@Provides
@Singleton
@MailboxDirectory
File provideMailboxDirectory(Application app) {
return app.getDir("mailbox", MODE_PRIVATE);
}
@Provides
@Singleton
@TorDirectory
@@ -190,7 +199,7 @@ public class AppModule {
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
AndroidRemovableDrivePluginFactory drive,
FeatureFlags featureFlags) {
MailboxPluginFactory mailbox, FeatureFlags featureFlags) {
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@@ -201,8 +210,10 @@ public class AppModule {
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
if (SDK_INT >= 19) return singletonList(drive);
else return emptyList();
List<SimplexPluginFactory> simplex = new ArrayList<>();
if (featureFlags.shouldEnableMailbox()) simplex.add(mailbox);
if (SDK_INT >= 19) simplex.add(drive);
return simplex;
}
@Override

View File

@@ -36,6 +36,7 @@ import static android.view.View.VISIBLE;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.widget.ImageViewCompat.setImageTintList;
import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@@ -47,7 +48,6 @@ import static org.briarproject.briar.android.util.UiUtils.showFragment;
public class MailboxStatusFragment extends Fragment {
static final String TAG = MailboxStatusFragment.class.getName();
private static final int NUM_FAILURES = 4;
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -59,8 +59,7 @@ public class MailboxStatusFragment extends Fragment {
private boolean showUnlinkWarning = true;
private ImageView imageView;
private TextView statusTitleView;
private TextView statusInfoView;
private TextView statusTitleView, statusMessageView, statusInfoView;
private Button wizardButton;
private Button unlinkButton;
private ProgressBar unlinkProgress;
@@ -95,6 +94,7 @@ public class MailboxStatusFragment extends Fragment {
imageView = v.findViewById(R.id.imageView);
statusTitleView = v.findViewById(R.id.statusTitleView);
statusMessageView = v.findViewById(R.id.statusMessageView);
statusInfoView = v.findViewById(R.id.statusInfoView);
viewModel.getStatus()
.observe(getViewLifecycleOwner(), this::onMailboxStateChanged);
@@ -133,29 +133,51 @@ public class MailboxStatusFragment extends Fragment {
@ColorRes int tintRes;
@DrawableRes int iconRes;
String title;
if (status.getAttemptsSinceSuccess() == 0) {
iconRes = R.drawable.ic_check_circle_outline;
title = getString(R.string.mailbox_status_connected_title);
tintRes = R.color.briar_brand_green;
showUnlinkWarning = true;
wizardButton.setVisibility(GONE);
} else if (!status.hasProblem(System.currentTimeMillis())) {
iconRes = R.drawable.ic_help_outline_white;
title = getString(R.string.mailbox_status_problem_title);
tintRes = R.color.briar_orange_500;
showUnlinkWarning = false;
wizardButton.setVisibility(VISIBLE);
} else {
String message = null;
if (status.hasProblem(System.currentTimeMillis())) {
tintRes = R.color.briar_red_500;
title = getString(R.string.mailbox_status_failure_title);
iconRes = R.drawable.alerts_and_states_error;
showUnlinkWarning = false;
wizardButton.setVisibility(VISIBLE);
} else if (status.getAttemptsSinceSuccess() > 0) {
iconRes = R.drawable.ic_help_outline_white;
title = getString(R.string.mailbox_status_problem_title);
tintRes = R.color.briar_orange_500;
showUnlinkWarning = false;
wizardButton.setVisibility(VISIBLE);
} else if (status.getMailboxCompatibility() < 0) {
tintRes = R.color.briar_red_500;
if (status.getMailboxCompatibility() == API_CLIENT_TOO_OLD) {
title = getString(R.string.mailbox_status_app_too_old_title);
message =
getString(R.string.mailbox_status_app_too_old_message);
} else {
title = getString(
R.string.mailbox_status_mailbox_too_old_title);
message = getString(
R.string.mailbox_status_mailbox_too_old_message);
}
iconRes = R.drawable.alerts_and_states_error;
showUnlinkWarning = true;
wizardButton.setVisibility(GONE);
} else {
iconRes = R.drawable.ic_check_circle_outline;
title = getString(R.string.mailbox_status_connected_title);
tintRes = R.color.briar_brand_green;
showUnlinkWarning = true;
wizardButton.setVisibility(GONE);
}
imageView.setImageResource(iconRes);
int color = getColor(requireContext(), tintRes);
setImageTintList(imageView, ColorStateList.valueOf(color));
statusTitleView.setText(title);
if (message == null) {
statusMessageView.setVisibility(GONE);
} else {
statusMessageView.setVisibility(VISIBLE);
statusMessageView.setText(message);
}
long lastSuccess = status.getTimeOfLastSuccess();
String lastConnectionText;

View File

@@ -208,16 +208,12 @@ class MailboxViewModel extends DbViewModel
LiveData<Boolean> checkConnection() {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
checkConnection(success -> {
liveData.postValue(success);
if (!success) onConnectionCheckFailure();
});
checkConnection(liveData::postValue);
return liveData;
}
void checkConnectionFromWizard() {
checkConnection(success -> {
if (!success) onConnectionCheckFailure();
boolean isOnline = isTorActive();
// make UI move back to status fragment by changing pairingState
pairingState.postEvent(new MailboxState.IsPaired(isOnline));
@@ -234,15 +230,6 @@ class MailboxViewModel extends DbViewModel
});
}
private void onConnectionCheckFailure() {
MailboxStatus lastStatus = status.getValue();
long lastSuccess = lastStatus == null ?
-1 : lastStatus.getTimeOfLastSuccess();
long now = System.currentTimeMillis();
// force failure screen
status.postValue(new MailboxStatus(now, lastSuccess, 999));
}
@UiThread
void unlink() {
ioExecutor.execute(() -> {

View File

@@ -7,7 +7,6 @@
package org.briarproject.briar.android.reporting;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.FeatureInfo;
@@ -19,7 +18,6 @@ import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Debug;
import android.os.Environment;
import org.briarproject.bramble.api.Pair;
@@ -29,6 +27,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
import org.briarproject.briar.api.android.MemoryStats;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import org.briarproject.briar.api.android.NetworkUsageMetrics.Metrics;
@@ -76,14 +75,14 @@ class BriarReportCollector {
}
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
String logs) {
String logs, MemoryStats memoryStats) {
ReportData reportData = new ReportData()
.add(getBasicInfo(t))
.add(getDeviceInfo());
if (t != null) reportData.add(getStacktrace(t));
return reportData
.add(getTimeInfo(appStartTime))
.add(getMemory())
.add(getMemory(memoryStats))
.add(getStorage())
.add(getConnectivity())
.add(getNetworkUsage())
@@ -154,26 +153,22 @@ class BriarReportCollector {
return format.format(new Date(time));
}
private ReportItem getMemory() {
private ReportItem getMemory(MemoryStats stats) {
MultiReportInfo memInfo = new MultiReportInfo();
// System memory
ActivityManager am = getSystemService(ctx, ActivityManager.class);
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
requireNonNull(am).getMemoryInfo(mem);
memInfo.add("SystemMemoryTotal", mem.totalMem);
memInfo.add("SystemMemoryFree", mem.availMem);
memInfo.add("SystemMemoryThreshold", mem.threshold);
memInfo.add("SystemMemoryLow", mem.lowMemory);
memInfo.add("SystemMemoryTotal", stats.systemMemoryTotal);
memInfo.add("SystemMemoryFree", stats.systemMemoryFree);
memInfo.add("SystemMemoryThreshold", stats.systemMemoryThreshold);
memInfo.add("SystemMemoryLow", stats.systemMemoryLow);
// Virtual machine memory
Runtime runtime = Runtime.getRuntime();
memInfo.add("VirtualMachineMemoryTotal", runtime.totalMemory());
memInfo.add("VirtualMachineMemoryFree", runtime.freeMemory());
memInfo.add("VirtualMachineMemoryMaximum", runtime.maxMemory());
memInfo.add("NativeHeapTotal", Debug.getNativeHeapSize());
memInfo.add("NativeHeapAllocated", Debug.getNativeHeapAllocatedSize());
memInfo.add("NativeHeapFree", Debug.getNativeHeapFreeSize());
memInfo.add("VirtualMachineMemoryTotal", stats.vmMemoryTotal);
memInfo.add("VirtualMachineMemoryFree", stats.vmMemoryFree);
memInfo.add("VirtualMachineMemoryMaximum", stats.vmMemoryMax);
memInfo.add("NativeHeapTotal", stats.nativeHeapTotal);
memInfo.add("NativeHeapAllocated", stats.nativeHeapAllocated);
memInfo.add("NativeHeapFree", stats.nativeHeapFree);
return new ReportItem("Memory", R.string.dev_report_memory, memInfo);
}

View File

@@ -15,6 +15,7 @@ import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.logout.HideUiActivity;
import org.briarproject.briar.api.android.MemoryStats;
import javax.inject.Inject;
@@ -37,6 +38,7 @@ public class CrashReportActivity extends BaseActivity
public static final String EXTRA_THROWABLE = "throwable";
public static final String EXTRA_APP_START_TIME = "appStartTime";
public static final String EXTRA_APP_LOGCAT = "logcat";
public static final String EXTRA_MEMORY_STATS = "memoryStats";
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -60,7 +62,9 @@ public class CrashReportActivity extends BaseActivity
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
viewModel.init(t, appStartTime, logKey, initialComment);
MemoryStats memoryStats =
(MemoryStats) intent.getSerializableExtra(EXTRA_MEMORY_STATS);
viewModel.init(t, appStartTime, logKey, initialComment, memoryStats);
viewModel.getShowReport().observeEvent(this, show -> {
if (show) displayFragment(true);
});
@@ -87,7 +91,7 @@ public class CrashReportActivity extends BaseActivity
exit();
}
void displayFragment(boolean showReportForm) {
private void displayFragment(boolean showReportForm) {
BaseFragment f;
if (showReportForm) {
f = new ReportFormFragment();
@@ -101,7 +105,7 @@ public class CrashReportActivity extends BaseActivity
.commit();
}
void exit() {
private void exit() {
if (!viewModel.isFeedback()) {
Intent i = new Intent(this, HideUiActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK

View File

@@ -18,6 +18,7 @@ import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.android.MemoryStats;
import org.briarproject.briar.api.android.NetworkUsageMetrics;
import org.json.JSONException;
@@ -84,7 +85,8 @@ class ReportViewModel extends AndroidViewModel {
}
void init(@Nullable Throwable t, long appStartTime,
@Nullable byte[] logKey, @Nullable String initialComment) {
@Nullable byte[] logKey, @Nullable String initialComment,
MemoryStats memoryStats) {
this.initialComment = initialComment;
isFeedback = t == null;
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
@@ -102,8 +104,8 @@ class ReportViewModel extends AndroidViewModel {
logHandler.getRecentLogRecords());
}
}
ReportData data =
collector.collectReportData(t, appStartTime, decryptedLogs);
ReportData data = collector.collectReportData(t, appStartTime,
decryptedLogs, memoryStats);
reportData.postValue(data);
}).start();
}
@@ -150,7 +152,7 @@ class ReportViewModel extends AndroidViewModel {
/**
* The content of the report that will be loaded after
* {@link #init(Throwable, long, byte[], String)} was called.
* {@link #init(Throwable, long, byte[], String, MemoryStats)} was called.
*/
LiveData<ReportData> getReportData() {
return reportData;

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.android.util;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -10,6 +12,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Debug;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -38,6 +41,7 @@ import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.reporting.FeedbackActivity;
import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.api.android.MemoryStats;
import java.util.Locale;
import java.util.logging.Logger;
@@ -113,6 +117,7 @@ import static org.briarproject.briar.android.TestingConstants.OLD_ANDROID_WARN_D
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_INITIAL_COMMENT;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_MEMORY_STATS;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
@MethodsNotNullByDefault
@@ -429,12 +434,27 @@ public class UiUtils {
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
@Nullable Long appStartTime, @Nullable byte[] logKey, @Nullable
String initialComment) {
// Collect memory stats from the current process, not the crash
// reporter process
ActivityManager am =
requireNonNull(getSystemService(ctx, ActivityManager.class));
MemoryInfo mem = new MemoryInfo();
am.getMemoryInfo(mem);
Runtime runtime = Runtime.getRuntime();
MemoryStats memoryStats = new MemoryStats(mem.totalMem,
mem.availMem, mem.threshold, mem.lowMemory,
runtime.totalMemory(), runtime.freeMemory(),
runtime.maxMemory(), Debug.getNativeHeapSize(),
Debug.getNativeHeapAllocatedSize(),
Debug.getNativeHeapFreeSize());
final Intent dialogIntent = new Intent(ctx, activity);
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra(EXTRA_THROWABLE, t);
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
dialogIntent.putExtra(EXTRA_INITIAL_COMMENT, initialComment);
dialogIntent.putExtra(EXTRA_MEMORY_STATS, memoryStats);
ctx.startActivity(dialogIntent);
}

View File

@@ -0,0 +1,42 @@
package org.briarproject.briar.api.android;
import java.io.Serializable;
import javax.annotation.concurrent.Immutable;
/**
* Memory usage stats to be included in feedback and crash reports. This class
* is {@link Serializable} so it can be passed from the crashed process to the
* crash reporter process.
*/
@Immutable
public class MemoryStats implements Serializable {
public final long systemMemoryTotal;
public final long systemMemoryFree;
public final long systemMemoryThreshold;
public final boolean systemMemoryLow;
public final long vmMemoryTotal;
public final long vmMemoryFree;
public final long vmMemoryMax;
public final long nativeHeapTotal;
public final long nativeHeapAllocated;
public final long nativeHeapFree;
public MemoryStats(long systemMemoryTotal, long systemMemoryFree,
long systemMemoryThreshold, boolean systemMemoryLow,
long vmMemoryTotal, long vmMemoryFree, long vmMemoryMax,
long nativeHeapTotal, long nativeHeapAllocated,
long nativeHeapFree) {
this.systemMemoryTotal = systemMemoryTotal;
this.systemMemoryFree = systemMemoryFree;
this.systemMemoryThreshold = systemMemoryThreshold;
this.systemMemoryLow = systemMemoryLow;
this.vmMemoryTotal = vmMemoryTotal;
this.vmMemoryFree = vmMemoryFree;
this.vmMemoryMax = vmMemoryMax;
this.nativeHeapTotal = nativeHeapTotal;
this.nativeHeapAllocated = nativeHeapAllocated;
this.nativeHeapFree = nativeHeapFree;
}
}

View File

@@ -31,12 +31,29 @@
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintBottom_toTopOf="@+id/statusMessageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="@string/mailbox_status_problem_title" />
<TextView
android:id="@+id/statusMessageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
tools:text="@string/mailbox_status_mailbox_too_old_message"
tools:visibility="visible" />
<org.briarproject.briar.android.view.BriarButton
android:id="@+id/checkButton"
android:layout_width="wrap_content"
@@ -46,7 +63,7 @@
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
app:text="@string/mailbox_status_check_button" />
<TextView

View File

@@ -29,7 +29,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine|textCapSentences"
android:maxLines="5"
tools:hint="@string/describe_crash" />
</com.google.android.material.textfield.TextInputLayout>

View File

@@ -640,6 +640,10 @@
<string name="mailbox_status_connected_title">Mailbox is running</string>
<string name="mailbox_status_problem_title">Briar is having trouble connecting to the Mailbox</string>
<string name="mailbox_status_failure_title">Mailbox is unavailable</string>
<string name="mailbox_status_app_too_old_title">Briar is too old</string>
<string name="mailbox_status_app_too_old_message">Update Briar to the latest version of the app and try again.</string>
<string name="mailbox_status_mailbox_too_old_title">Mailbox is too old</string>
<string name="mailbox_status_mailbox_too_old_message">Update your Mailbox to the latest version of the app and try again.</string>
<string name="mailbox_status_check_button">Check Connection</string>
<!-- Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Last connection: %s</string>

View File

@@ -59,7 +59,12 @@ void jarFactory(Jar jarTask, jarArchitecture) {
}
{
it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
String[] architectures = ["linux-aarch64", "linux-armhf", "linux-x86_64"]
String[] architectures = [
"linux-aarch64",
"linux-armhf",
"linux-x86_64",
"windows-x86_64"
]
for (String arch : architectures) {
if (arch != jarArchitecture) {
exclude "obfs4proxy_" + arch + ".zip"
@@ -112,6 +117,10 @@ task x86LinuxJar(type: Jar) {
jarFactory(it, 'linux-x86_64')
}
task windowsJar(type: Jar) {
jarFactory(it, 'windows-x86_64')
}
task linuxJars {
dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar)
}

View File

@@ -6,6 +6,7 @@ import dagger.Provides
import org.briarproject.bramble.account.AccountModule
import org.briarproject.bramble.api.FeatureFlags
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.mailbox.MailboxDirectory
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT
@@ -20,6 +21,7 @@ import org.briarproject.bramble.event.DefaultEventExecutorModule
import org.briarproject.bramble.network.JavaNetworkModule
import org.briarproject.bramble.plugin.tor.CircumventionModule
import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory
import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
import org.briarproject.bramble.socks.SocksModule
import org.briarproject.bramble.system.ClockModule
import org.briarproject.bramble.system.DefaultTaskSchedulerModule
@@ -28,6 +30,7 @@ import org.briarproject.bramble.system.DesktopSecureRandomModule
import org.briarproject.bramble.system.JavaSystemModule
import org.briarproject.bramble.util.OsUtils.isLinux
import org.briarproject.bramble.util.OsUtils.isMac
import org.briarproject.bramble.util.OsUtils.isWindows
import org.briarproject.briar.headless.blogs.HeadlessBlogModule
import org.briarproject.briar.headless.contact.HeadlessContactModule
import org.briarproject.briar.headless.event.HeadlessEventModule
@@ -71,6 +74,12 @@ internal class HeadlessModule(private val appDir: File) {
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
@MailboxDirectory
internal fun provideMailboxDirectory(): File {
return File(appDir, "mailbox")
}
@Provides
@TorDirectory
internal fun provideTorDirectory(): File {
@@ -87,9 +96,15 @@ internal class HeadlessModule(private val appDir: File) {
@Provides
@Singleton
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
val duplex: List<DuplexPluginFactory> =
if (isLinux() || isMac()) listOf(tor) else emptyList()
internal fun providePluginConfig(
unixTor: UnixTorPluginFactory,
winTor: WindowsTorPluginFactory
): PluginConfig {
val duplex: List<DuplexPluginFactory> = when {
isLinux() || isMac() -> listOf(unixTor)
isWindows() -> listOf(winTor)
else -> emptyList()
}
return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex
override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()

View File

@@ -8,6 +8,8 @@ import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int
import org.bouncycastle.util.encoders.Base64.toBase64String
import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.bramble.util.OsUtils.isLinux
import org.briarproject.bramble.util.OsUtils.isMac
import org.briarproject.briar.BriarCoreEagerSingletons
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
import java.io.File
@@ -90,11 +92,13 @@ private class Main : CliktCommand(
} else if (!file.isDirectory) {
throw IOException("Data dir is not a directory: ${file.absolutePath}")
}
val perms = HashSet<PosixFilePermission>()
perms.add(OWNER_READ)
perms.add(OWNER_WRITE)
perms.add(OWNER_EXECUTE)
setPosixFilePermissions(file.toPath(), perms)
if (isLinux() || isMac()) {
val perms = HashSet<PosixFilePermission>()
perms.add(OWNER_READ)
perms.add(OWNER_WRITE)
perms.add(OWNER_EXECUTE)
setPosixFilePermissions(file.toPath(), perms)
}
return file
}

View File

@@ -5,6 +5,7 @@ import dagger.Module
import dagger.Provides
import org.briarproject.bramble.account.AccountModule
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.mailbox.MailboxDirectory
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT
import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT
@@ -68,6 +69,12 @@ internal class HeadlessTestModule(private val appDir: File) {
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
@MailboxDirectory
internal fun provideMailboxDirectory(): File {
return File(appDir, "mailbox")
}
@Provides
@TorSocksPort
internal fun provideTorSocksPort(): Int = DEFAULT_SOCKS_PORT