diff --git a/bramble-android/build.gradle b/bramble-android/build.gradle
index 719309088..4a3083747 100644
--- a/bramble-android/build.gradle
+++ b/bramble-android/build.gradle
@@ -40,6 +40,8 @@ configurations {
}
dependencies {
+ api 'org.briarproject:dont-kill-me-lib:0.2.6'
+
// In theory this dependency shouldn't be needed, but without it Android Studio's linter will
// complain about unresolved symbols for bramble-api test classes in bramble-android tests,
// even though the bramble-api test classes are provided by the testImplementation dependency
diff --git a/bramble-android/src/main/java/org/briarproject/android/dontkillmelib/wakelock/AndroidWakeLockModule.java b/bramble-android/src/main/java/org/briarproject/android/dontkillmelib/wakelock/AndroidWakeLockModule.java
new file mode 100644
index 000000000..a8dba5dd1
--- /dev/null
+++ b/bramble-android/src/main/java/org/briarproject/android/dontkillmelib/wakelock/AndroidWakeLockModule.java
@@ -0,0 +1,17 @@
+package org.briarproject.android.dontkillmelib.wakelock;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class AndroidWakeLockModule {
+
+ @Provides
+ @Singleton
+ AndroidWakeLockManager provideWakeLockManager(
+ AndroidWakeLockManagerImpl wakeLockManager) {
+ return wakeLockManager;
+ }
+}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/BrambleAndroidModule.java b/bramble-android/src/main/java/org/briarproject/bramble/BrambleAndroidModule.java
index 7859bd652..f38219880 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/BrambleAndroidModule.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/BrambleAndroidModule.java
@@ -1,9 +1,10 @@
package org.briarproject.bramble;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockModule;
import org.briarproject.bramble.battery.AndroidBatteryModule;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.network.AndroidNetworkModule;
-import org.briarproject.bramble.plugin.tor.CircumventionModule;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionModule;
import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.AndroidSystemModule;
@@ -19,6 +20,7 @@ import dagger.Module;
AndroidSystemModule.class,
AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class,
+ AndroidWakeLockModule.class,
DefaultThreadFactoryModule.class,
CircumventionModule.class,
DnsModule.class,
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLock.java
deleted file mode 100644
index 2ef13da48..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLock.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.briarproject.bramble.api.system;
-
-import org.briarproject.nullsafety.NotNullByDefault;
-
-@NotNullByDefault
-public interface AndroidWakeLock {
-
- /**
- * Acquires the wake lock. This has no effect if the wake lock has already
- * been acquired.
- */
- void acquire();
-
- /**
- * Releases the wake lock. This has no effect if the wake lock has already
- * been released.
- */
- void release();
-}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockManager.java b/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockManager.java
deleted file mode 100644
index a44a088a5..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.briarproject.bramble.api.system;
-
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.util.concurrent.Executor;
-
-@NotNullByDefault
-public interface AndroidWakeLockManager {
-
- /**
- * Creates a wake lock with the given tag. The tag is only used for
- * logging; the underlying OS wake lock will use its own tag.
- */
- AndroidWakeLock createWakeLock(String tag);
-
- /**
- * Runs the given task while holding a wake lock.
- */
- void runWakefully(Runnable r, String tag);
-
- /**
- * Submits the given task to the given executor while holding a wake lock.
- * The lock is released when the task completes, or if an exception is
- * thrown while submitting or running the task.
- */
- void executeWakefully(Runnable r, Executor executor, String tag);
-
- /**
- * Starts a dedicated thread to run the given task asynchronously. A wake
- * lock is acquired before starting the thread and released when the task
- * completes, or if an exception is thrown while starting the thread or
- * running the task.
- *
- * This method should only be used for lifecycle management tasks that
- * can't be run on an executor.
- */
- void executeWakefully(Runnable r, String tag);
-}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothConnectionFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothConnectionFactory.java
index ecc468258..3ad518fb4 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothConnectionFactory.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothConnectionFactory.java
@@ -2,10 +2,10 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java
index 6ea75cb27..2e3a2ac69 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.app.Application;
import android.bluetooth.BluetoothSocket;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -13,7 +14,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidExecutor;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java
index 297c11021..fbfee2791 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java
@@ -2,11 +2,11 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
-import org.briarproject.bramble.api.system.AndroidWakeLock;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java
index 59d0ff03e..76cbfd397 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java
@@ -2,9 +2,11 @@ package org.briarproject.bramble.plugin.tor;
import android.app.Application;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
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.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -13,11 +15,12 @@ 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.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
+import org.briarproject.bramble.plugin.tor.wrapper.AndroidTorWrapper;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
@@ -28,6 +31,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
+import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
@Immutable
@@ -39,13 +43,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
+ @EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
- ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -55,8 +59,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@TorControlPort int torControlPort,
Application app,
AndroidWakeLockManager wakeLockManager) {
- super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
- eventBus, torSocketFactory, backoffFactory, resourceProvider,
+ super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
+ locationUtils, eventBus, torSocketFactory, backoffFactory,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app;
@@ -79,12 +83,18 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
- return new AndroidTorPlugin(ioExecutor,
- wakefulIoExecutor, app, networkManager, locationUtils,
- torSocketFactory, clock, resourceProvider,
- circumventionProvider, batteryManager, wakeLockManager,
- backoff, torRendezvousCrypto, callback, architecture,
- MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
- torControlPort);
+ TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
+ ioExecutor, eventExecutor, architecture, torDirectory,
+ torSocksPort, torControlPort);
+ // Android versions 7.1 and newer can verify Let's Encrypt TLS certs
+ // signed with the IdentTrust DST Root X3 certificate. Older versions
+ // of Android consider the certificate to have expired at the end of
+ // September 2021.
+ boolean canVerifyLetsEncryptCerts = SDK_INT >= 25;
+ return new TorPlugin(ioExecutor, wakefulIoExecutor,
+ networkManager, locationUtils, torSocketFactory,
+ circumventionProvider, batteryManager, backoff,
+ torRendezvousCrypto, tor, callback, MAX_LATENCY,
+ MAX_IDLE_TIME, canVerifyLetsEncryptCerts);
}
}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java
similarity index 66%
rename from bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java
rename to bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java
index 1789ffcb0..3788ce923 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AndroidTorWrapper.java
@@ -1,46 +1,39 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Build;
-import org.briarproject.bramble.api.battery.BatteryManager;
-import org.briarproject.bramble.api.network.NetworkManager;
-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;
-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.util.AndroidUtils;
-import org.briarproject.nullsafety.MethodsNotNullByDefault;
-import org.briarproject.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
+import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
-import javax.net.SocketFactory;
-
-import androidx.annotation.ChecksSdkIntAtLeast;
-
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-class AndroidTorPlugin extends TorPlugin {
+/**
+ * A Tor wrapper for the Android operating system.
+ */
+@NotNullByDefault
+public class AndroidTorWrapper extends AbstractTorWrapper {
private static final List LIBRARY_ARCHITECTURES =
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
@@ -50,37 +43,39 @@ class AndroidTorPlugin extends TorPlugin {
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
private static final Logger LOG =
- getLogger(AndroidTorPlugin.class.getName());
+ getLogger(AndroidTorWrapper.class.getName());
private final Application app;
private final AndroidWakeLock wakeLock;
private final File torLib, obfs4Lib, snowflakeLib;
- AndroidTorPlugin(Executor ioExecutor,
- Executor wakefulIoExecutor,
- Application app,
- NetworkManager networkManager,
- LocationUtils locationUtils,
- SocketFactory torSocketFactory,
- Clock clock,
- ResourceProvider resourceProvider,
- CircumventionProvider circumventionProvider,
- BatteryManager batteryManager,
+ /**
+ * @param app The application instance.
+ * @param wakeLockManager The interface for managing a shared wake lock.
+ * @param ioExecutor The wrapper will use this executor to run IO tasks,
+ * some of which may run for the lifetime of the wrapper, so the executor
+ * should have an unlimited thread pool.
+ * @param eventExecutor The wrapper will use this executor to call the
+ * {@link Observer observer} (if any). To ensure that events are observed
+ * in the order they occur, this executor should have a single thread (eg
+ * the app's main thread).
+ * @param architecture The processor architecture of the Tor and pluggable
+ * transport binaries.
+ * @param torDirectory The directory where the Tor process should keep its
+ * state.
+ * @param torSocksPort The port number to use for Tor's SOCKS port.
+ * @param torControlPort The port number to use for Tor's control port.
+ */
+ public AndroidTorWrapper(Application app,
AndroidWakeLockManager wakeLockManager,
- Backoff backoff,
- TorRendezvousCrypto torRendezvousCrypto,
- PluginCallback callback,
+ Executor ioExecutor,
+ Executor eventExecutor,
String architecture,
- long maxLatency,
- int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
- super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
- torSocketFactory, clock, resourceProvider,
- circumventionProvider, batteryManager, backoff,
- torRendezvousCrypto, callback, architecture, maxLatency,
- maxIdleTime, torDirectory, torSocksPort, torControlPort);
+ super(ioExecutor, eventExecutor, architecture, torDirectory,
+ torSocksPort, torControlPort);
this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
@@ -106,22 +101,30 @@ class AndroidTorPlugin extends TorPlugin {
}
@Override
- protected void enableNetwork(boolean enable) throws IOException {
+ public InputStream getResourceInputStream(String name, String extension) {
+ Resources res = app.getResources();
+ // Extension is ignored on Android, resources are retrieved without it
+ int resId = res.getIdentifier(name, "raw", app.getPackageName());
+ return res.openRawResource(resId);
+ }
+
+ @Override
+ public void enableNetwork(boolean enable) throws IOException {
if (enable) wakeLock.acquire();
- super.enableNetwork(enable);
- if (!enable) wakeLock.release();
+ try {
+ super.enableNetwork(enable);
+ } finally {
+ if (!enable) wakeLock.release();
+ }
}
@Override
- @ChecksSdkIntAtLeast(api = 25)
- protected boolean canVerifyLetsEncryptCerts() {
- return SDK_INT >= 25;
- }
-
- @Override
- public void stop() {
- super.stop();
- wakeLock.release();
+ public void stop() throws IOException {
+ try {
+ super.stop();
+ } finally {
+ wakeLock.release();
+ }
}
@Override
@@ -136,8 +139,8 @@ class AndroidTorPlugin extends TorPlugin {
@Override
protected File getSnowflakeExecutableFile() {
- return snowflakeLib.exists()
- ? snowflakeLib : super.getSnowflakeExecutableFile();
+ return snowflakeLib.exists() ? snowflakeLib
+ : super.getSnowflakeExecutableFile();
}
@Override
@@ -184,6 +187,7 @@ class AndroidTorPlugin extends TorPlugin {
}
List libPaths = getSupportedLibraryPaths(libName);
for (File apk : findApkFiles(sourceDir)) {
+ @SuppressWarnings("IOStreamConstructor")
ZipInputStream zin = new ZipInputStream(new FileInputStream(apk));
for (ZipEntry e = zin.getNextEntry(); e != null;
e = zin.getNextEntry()) {
@@ -228,11 +232,22 @@ class AndroidTorPlugin extends TorPlugin {
*/
private List getSupportedLibraryPaths(String libName) {
List architectures = new ArrayList<>();
- for (String abi : AndroidUtils.getSupportedArchitectures()) {
+ for (String abi : getSupportedArchitectures()) {
if (LIBRARY_ARCHITECTURES.contains(abi)) {
architectures.add("lib/" + abi + "/" + libName);
}
}
return architectures;
}
+
+ private Collection getSupportedArchitectures() {
+ List abis = new ArrayList<>();
+ if (SDK_INT >= 21) {
+ abis.addAll(asList(Build.SUPPORTED_ABIS));
+ } else {
+ abis.add(Build.CPU_ABI);
+ if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2);
+ }
+ return abis;
+ }
}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
index 714d1ab83..96aadb506 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidSystemModule.java
@@ -3,7 +3,6 @@ package org.briarproject.bramble.system;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AndroidExecutor;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider;
@@ -69,11 +68,4 @@ public class AndroidSystemModule {
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
return provider;
}
-
- @Provides
- @Singleton
- AndroidWakeLockManager provideWakeLockManager(
- AndroidWakeLockManagerImpl wakeLockManager) {
- return wakeLockManager;
- }
}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskScheduler.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskScheduler.java
index 92fb8c4d5..22f8d0df6 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskScheduler.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskScheduler.java
@@ -8,10 +8,10 @@ import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.system.AlarmListener;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.nullsafety.NotNullByDefault;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskSchedulerModule.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskSchedulerModule.java
index 2dcbb551c..403fd8f9d 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskSchedulerModule.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskSchedulerModule.java
@@ -2,9 +2,9 @@ package org.briarproject.bramble.system;
import android.app.Application;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AlarmListener;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.ScheduledExecutorService;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java
deleted file mode 100644
index 5da499d1e..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.briarproject.bramble.system;
-
-import org.briarproject.bramble.api.system.AndroidWakeLock;
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Logger;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.ThreadSafe;
-
-import static java.util.logging.Level.FINE;
-import static java.util.logging.Logger.getLogger;
-
-/**
- * A wrapper around a {@link SharedWakeLock} that provides the more convenient
- * semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release()
- * don't need to be balanced).
- */
-@ThreadSafe
-@NotNullByDefault
-class AndroidWakeLockImpl implements AndroidWakeLock {
-
- private static final Logger LOG =
- getLogger(AndroidWakeLockImpl.class.getName());
-
- private static final AtomicInteger INSTANCE_ID = new AtomicInteger(0);
-
- private final SharedWakeLock sharedWakeLock;
- private final String tag;
-
- private final Object lock = new Object();
- @GuardedBy("lock")
- private boolean held = false;
-
- AndroidWakeLockImpl(SharedWakeLock sharedWakeLock, String tag) {
- this.sharedWakeLock = sharedWakeLock;
- this.tag = tag + "_" + INSTANCE_ID.getAndIncrement();
- }
-
- @Override
- public void acquire() {
- synchronized (lock) {
- if (held) {
- if (LOG.isLoggable(FINE)) {
- LOG.fine(tag + " already acquired");
- }
- } else {
- if (LOG.isLoggable(FINE)) {
- LOG.fine(tag + " acquiring shared wake lock");
- }
- held = true;
- sharedWakeLock.acquire();
- }
- }
- }
-
- @Override
- public void release() {
- synchronized (lock) {
- if (held) {
- if (LOG.isLoggable(FINE)) {
- LOG.fine(tag + " releasing shared wake lock");
- }
- held = false;
- sharedWakeLock.release();
- } else {
- if (LOG.isLoggable(FINE)) {
- LOG.fine(tag + " already released");
- }
- }
- }
- }
-}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockManagerImpl.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockManagerImpl.java
deleted file mode 100644
index 8c0f444e0..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockManagerImpl.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.briarproject.bramble.system;
-
-import android.app.Application;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.PowerManager;
-
-import org.briarproject.bramble.api.system.AndroidWakeLock;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.inject.Inject;
-
-import static android.content.Context.POWER_SERVICE;
-import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.briarproject.nullsafety.NullSafety.requireNonNull;
-
-@NotNullByDefault
-class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
-
- /**
- * How often to replace the wake lock.
- */
- private static final long LOCK_DURATION_MS = MINUTES.toMillis(1);
-
- /**
- * Automatically release the lock this many milliseconds after it's due
- * to have been replaced and released.
- */
- private static final long SAFETY_MARGIN_MS = SECONDS.toMillis(30);
-
- private final SharedWakeLock sharedWakeLock;
-
- @Inject
- AndroidWakeLockManagerImpl(Application app,
- ScheduledExecutorService scheduledExecutorService) {
- PowerManager powerManager = (PowerManager)
- requireNonNull(app.getSystemService(POWER_SERVICE));
- String tag = getWakeLockTag(app);
- sharedWakeLock = new RenewableWakeLock(powerManager,
- scheduledExecutorService, PARTIAL_WAKE_LOCK, tag,
- LOCK_DURATION_MS, SAFETY_MARGIN_MS);
- }
-
- @Override
- public AndroidWakeLock createWakeLock(String tag) {
- return new AndroidWakeLockImpl(sharedWakeLock, tag);
- }
-
- @Override
- public void runWakefully(Runnable r, String tag) {
- AndroidWakeLock wakeLock = createWakeLock(tag);
- wakeLock.acquire();
- try {
- r.run();
- } finally {
- wakeLock.release();
- }
- }
-
- @Override
- public void executeWakefully(Runnable r, Executor executor, String tag) {
- AndroidWakeLock wakeLock = createWakeLock(tag);
- wakeLock.acquire();
- try {
- executor.execute(() -> {
- try {
- r.run();
- } finally {
- // Release the wake lock if the task throws an exception
- wakeLock.release();
- }
- });
- } catch (Exception e) {
- // Release the wake lock if the executor throws an exception when
- // we submit the task (in which case the release() call above won't
- // happen)
- wakeLock.release();
- throw e;
- }
- }
-
- @Override
- public void executeWakefully(Runnable r, String tag) {
- AndroidWakeLock wakeLock = createWakeLock(tag);
- wakeLock.acquire();
- try {
- new Thread(() -> {
- try {
- r.run();
- } finally {
- wakeLock.release();
- }
- }).start();
- } catch (Exception e) {
- wakeLock.release();
- throw e;
- }
- }
-
- private String getWakeLockTag(Context ctx) {
- PackageManager pm = ctx.getPackageManager();
- if (isInstalled(pm, "com.huawei.powergenie")) {
- return "LocationManagerService";
- } else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
- return "AudioIn";
- }
- return ctx.getPackageName();
- }
-
- private boolean isInstalled(PackageManager pm, String packageName) {
- try {
- pm.getPackageInfo(packageName, 0);
- return true;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
-}
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakefulIoExecutorModule.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakefulIoExecutorModule.java
index 994b50483..d8048d0ba 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakefulIoExecutorModule.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakefulIoExecutorModule.java
@@ -1,7 +1,7 @@
package org.briarproject.bramble.system;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.util.concurrent.Executor;
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java
deleted file mode 100644
index 498c3e8d4..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.briarproject.bramble.system;
-
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.ThreadSafe;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.logging.Level.FINE;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static java.util.logging.Logger.getLogger;
-import static org.briarproject.nullsafety.NullSafety.requireNonNull;
-
-@ThreadSafe
-@NotNullByDefault
-class RenewableWakeLock implements SharedWakeLock {
-
- private static final Logger LOG =
- getLogger(RenewableWakeLock.class.getName());
-
- private final PowerManager powerManager;
- private final ScheduledExecutorService scheduledExecutorService;
- private final int levelAndFlags;
- private final String tag;
- private final long durationMs, safetyMarginMs;
-
- private final Object lock = new Object();
- @GuardedBy("lock")
- @Nullable
- private WakeLock wakeLock;
- @GuardedBy("lock")
- @Nullable
- private Future> future;
- @GuardedBy("lock")
- private int refCount = 0;
- @GuardedBy("lock")
- private long acquired = 0;
-
- RenewableWakeLock(PowerManager powerManager,
- ScheduledExecutorService scheduledExecutorService,
- int levelAndFlags,
- String tag,
- long durationMs,
- long safetyMarginMs) {
- this.powerManager = powerManager;
- this.scheduledExecutorService = scheduledExecutorService;
- this.levelAndFlags = levelAndFlags;
- this.tag = tag;
- this.durationMs = durationMs;
- this.safetyMarginMs = safetyMarginMs;
- }
-
- @Override
- public void acquire() {
- synchronized (lock) {
- refCount++;
- if (refCount == 1) {
- if (LOG.isLoggable(INFO)) {
- LOG.info("Acquiring wake lock " + tag);
- }
- wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
- // We do our own reference counting so we can replace the lock
- // TODO: Check whether using a ref-counted wake lock affects
- // power management apps
- wakeLock.setReferenceCounted(false);
- wakeLock.acquire(durationMs + safetyMarginMs);
- future = scheduledExecutorService.schedule(this::renew,
- durationMs, MILLISECONDS);
- acquired = android.os.SystemClock.elapsedRealtime();
- } else if (LOG.isLoggable(FINE)) {
- LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
- }
- }
- }
-
- private void renew() {
- if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag);
- synchronized (lock) {
- if (wakeLock == null) {
- LOG.info("Already released");
- return;
- }
- if (LOG.isLoggable(FINE)) {
- LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
- }
- long now = android.os.SystemClock.elapsedRealtime();
- long expiry = acquired + durationMs + safetyMarginMs;
- if (now > expiry && LOG.isLoggable(WARNING)) {
- LOG.warning("Wake lock expired " + (now - expiry) + " ms ago");
- }
- WakeLock oldWakeLock = wakeLock;
- wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
- wakeLock.setReferenceCounted(false);
- wakeLock.acquire(durationMs + safetyMarginMs);
- oldWakeLock.release();
- future = scheduledExecutorService.schedule(this::renew, durationMs,
- MILLISECONDS);
- acquired = now;
- }
- }
-
- @Override
- public void release() {
- synchronized (lock) {
- refCount--;
- if (refCount == 0) {
- if (LOG.isLoggable(INFO)) {
- LOG.info("Releasing wake lock " + tag);
- }
- requireNonNull(future).cancel(false);
- future = null;
- requireNonNull(wakeLock).release();
- wakeLock = null;
- acquired = 0;
- } else if (LOG.isLoggable(FINE)) {
- LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
- }
- }
- }
-}
-
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java
deleted file mode 100644
index d8c430eec..000000000
--- a/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.briarproject.bramble.system;
-
-import org.briarproject.bramble.api.system.AndroidWakeLock;
-import org.briarproject.nullsafety.NotNullByDefault;
-
-@NotNullByDefault
-interface SharedWakeLock {
-
- /**
- * Acquires the wake lock. This increments the wake lock's reference count,
- * so unlike {@link AndroidWakeLock#acquire()} every call to this method
- * must be followed by a balancing call to {@link #release()}.
- */
- void acquire();
-
- /**
- * Releases the wake lock. This decrements the wake lock's reference count,
- * so unlike {@link AndroidWakeLock#release()} every call to this method
- * must follow a balancing call to {@link #acquire()}.
- */
- void release();
-}
diff --git a/bramble-android/witness.gradle b/bramble-android/witness.gradle
index 62fadac3f..f87138d64 100644
--- a/bramble-android/witness.gradle
+++ b/bramble-android/witness.gradle
@@ -24,6 +24,8 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'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:dont-kill-me-lib:0.2.6:dont-kill-me-lib-0.2.6.aar:8a4cc201143227c0865c2edfba035f71109bf02e1ab26444fa3e42d3c569960f',
+ 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
'org.briarproject:tor-android:0.4.7.13-2:tor-android-0.4.7.13-2.jar:453fd463b234a2104edd7f0d02d0649cbb5c5efbe47a76df3828f55a3f90f8b5',
@@ -37,11 +39,12 @@ dependencyVerification {
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c',
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
- 'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
+ 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
+ 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0:kotlin-stdlib-jdk7-1.8.0.jar:4c889d1d9803f5f2eb6c1592a6b7e62369ac7660c9eee15aba16fec059163666',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
- 'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
+ 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
index 64b53e003..6467d185c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java
@@ -40,9 +40,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
@@ -288,8 +288,10 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback {
private final TransportId id;
- private final AtomicReference state =
- new AtomicReference<>(STARTING_STOPPING);
+ private final Object stateLock = new Object();
+
+ @GuardedBy("lock")
+ private State state = STARTING_STOPPING;
private Callback(TransportId id) {
this.id = id;
@@ -343,22 +345,26 @@ class PluginManagerImpl implements PluginManager, Service {
@Override
public void pluginStateChanged(State newState) {
- State oldState = state.getAndSet(newState);
- if (newState != oldState) {
- if (LOG.isLoggable(INFO)) {
- LOG.info(id + " changed from state " + oldState
- + " to " + newState);
+ synchronized (stateLock) {
+ if (newState != state) {
+ State oldState = state;
+ state = newState;
+ if (LOG.isLoggable(INFO)) {
+ LOG.info(id + " changed from state " + oldState
+ + " to " + newState);
+ }
+ eventBus.broadcast(new TransportStateEvent(id, newState));
+ if (newState == ACTIVE) {
+ eventBus.broadcast(new TransportActiveEvent(id));
+ } else if (oldState == ACTIVE) {
+ eventBus.broadcast(new TransportInactiveEvent(id));
+ }
+ } else if (newState == DISABLED) {
+ // Broadcast an event even though the state hasn't changed,
+ // as the reasons for the plugin being disabled may have
+ // changed
+ eventBus.broadcast(new TransportStateEvent(id, newState));
}
- eventBus.broadcast(new TransportStateEvent(id, newState));
- if (newState == ACTIVE) {
- eventBus.broadcast(new TransportActiveEvent(id));
- } else if (oldState == ACTIVE) {
- eventBus.broadcast(new TransportInactiveEvent(id));
- }
- } else if (newState == DISABLED) {
- // Broadcast an event even though the state hasn't changed, as
- // the reasons for the plugin being disabled may have changed
- eventBus.broadcast(new TransportStateEvent(id, newState));
}
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
index 75579e4cb..1c6db7698 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
@@ -1,8 +1,5 @@
package org.briarproject.bramble.plugin.tor;
-import net.freehaven.tor.control.EventHandler;
-import net.freehaven.tor.control.TorControlConnection;
-
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.battery.BatteryManager;
@@ -27,30 +24,23 @@ import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
-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.plugin.tor.CircumventionProvider.BridgeType;
-import org.briarproject.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.HiddenServiceProperties;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.Observer;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState;
+import org.briarproject.nullsafety.InterfaceNotNullByDefault;
import org.briarproject.nullsafety.NotNullByDefault;
-import org.briarproject.nullsafety.ParametersNotNullByDefault;
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
-import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
@@ -63,13 +53,9 @@ import javax.net.SocketFactory;
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.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
-import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
-import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
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;
@@ -91,36 +77,19 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
-import static org.briarproject.bramble.util.IoUtils.copyAndClose;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
-import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
-import static org.briarproject.nullsafety.NullSafety.requireNonNull;
-@MethodsNotNullByDefault
-@ParametersNotNullByDefault
-abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
+@InterfaceNotNullByDefault
+class TorPlugin implements DuplexPlugin, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
- private static final String[] EVENTS = {
- "CIRC",
- "ORCONN",
- "STATUS_GENERAL",
- "STATUS_CLIENT",
- "HS_DESC",
- "NOTICE",
- "WARN",
- "ERR"
- };
- private static final String OWNER = "__OwningControllerProcess";
- private static final int COOKIE_TIMEOUT_MS = 3000;
- private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor;
@@ -129,91 +98,75 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
- private final Clock clock;
+ private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
+ private final TorWrapper tor;
private final PluginCallback callback;
- private final String architecture;
- private final CircumventionProvider circumventionProvider;
- private final ResourceProvider resourceProvider;
private final long maxLatency;
private final int maxIdleTime;
+ private final boolean canVerifyLetsEncryptCerts;
private final int socketTimeout;
- private final File torDirectory;
- private final File configFile;
- private final int torSocksPort;
- private final int torControlPort;
- private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
- private volatile Socket controlSocket = null;
- private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
- protected abstract int getProcessId();
-
- protected abstract long getLastUpdateTime();
-
TorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
- Clock clock,
- ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
+ TorWrapper tor,
PluginCallback callback,
- String architecture,
long maxLatency,
int maxIdleTime,
- File torDirectory,
- int torSocksPort,
- int torControlPort) {
+ boolean canVerifyLetsEncryptCerts) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.torSocketFactory = torSocketFactory;
- this.clock = clock;
- this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto;
+ this.tor = tor;
this.callback = callback;
- this.architecture = architecture;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
- if (maxIdleTime > Integer.MAX_VALUE / 2)
+ this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
+ if (maxIdleTime > Integer.MAX_VALUE / 2) {
socketTimeout = Integer.MAX_VALUE;
- else socketTimeout = maxIdleTime * 2;
- this.torDirectory = torDirectory;
- this.torSocksPort = torSocksPort;
- this.torControlPort = torControlPort;
- configFile = new File(torDirectory, "torrc");
- doneFile = new File(torDirectory, "done");
- cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
+ } else {
+ socketTimeout = maxIdleTime * 2;
+ }
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1);
- }
+ tor.setObserver(new Observer() {
- protected File getTorExecutableFile() {
- return new File(torDirectory, "tor");
- }
+ @Override
+ public void onState(TorState torState) {
+ State s = state.getState(torState);
+ if (s == ACTIVE) backoff.reset();
+ callback.pluginStateChanged(s);
+ }
- protected File getObfs4ExecutableFile() {
- return new File(torDirectory, "obfs4proxy");
- }
+ @Override
+ public void onBootstrapPercentage(int percentage) {
+ }
- protected File getSnowflakeExecutableFile() {
- return new File(torDirectory, "snowflake");
+ @Override
+ public void onHsDescriptorUpload(String onion) {
+ }
+ });
}
@Override
@@ -234,89 +187,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
- if (!torDirectory.exists()) {
- if (!torDirectory.mkdirs()) {
- LOG.warning("Could not create Tor directory.");
- throw new PluginException();
- }
- }
// Load the settings
settings = callback.getSettings();
+ // Start Tor
try {
- // Install or update the assets if necessary
- if (!assetsAreUpToDate()) installAssets();
- // Start from the default config every time
- extract(getConfigInputStream(), configFile);
- } catch (IOException e) {
- throw new PluginException(e);
- }
- if (cookieFile.exists() && !cookieFile.delete())
- LOG.warning("Old auth cookie not deleted");
- // Start a new Tor process
- LOG.info("Starting Tor");
- File torFile = getTorExecutableFile();
- String torPath = torFile.getAbsolutePath();
- String configPath = configFile.getAbsolutePath();
- String pid = String.valueOf(getProcessId());
- Process torProcess;
- ProcessBuilder pb =
- new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
- Map 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);
- }
- try {
- // 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) {
- if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
- LOG.warning("Auth cookie not created");
- if (LOG.isLoggable(INFO)) listFiles(torDirectory);
- throw new PluginException();
- }
- //noinspection BusyWait
- Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
- }
- LOG.info("Auth cookie created");
+ tor.start();
} catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt();
throw new PluginException();
- }
- try {
- // Open a control connection and authenticate using the cookie file
- controlSocket = new Socket("127.0.0.1", torControlPort);
- controlConnection = new TorControlConnection(controlSocket);
- controlConnection.authenticate(read(cookieFile));
- // Tell Tor to exit when the control connection is closed
- controlConnection.takeOwnership();
- controlConnection.resetConf(singletonList(OWNER));
- // Register to receive events from the Tor process
- controlConnection.setEventHandler(this);
- controlConnection.setEvents(asList(EVENTS));
- // Check whether Tor has already bootstrapped
- String info = controlConnection.getInfo("status/bootstrap-phase");
- if (info != null && info.contains("PROGRESS=100")) {
- LOG.info("Tor has already bootstrapped");
- state.setBootstrapped();
- }
- // Check whether Tor has already built a circuit
- info = controlConnection.getInfo("status/circuit-established");
- if ("1".equals(info)) {
- LOG.info("Tor has already built a circuit");
- state.setCircuitBuilt(true);
- }
} catch (IOException e) {
throw new PluginException(e);
}
- state.setStarted();
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
@@ -324,130 +206,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind();
}
- private boolean assetsAreUpToDate() {
- return doneFile.lastModified() > getLastUpdateTime();
- }
-
- private void installAssets() throws IOException {
- // The done file may already exist from a previous installation
- //noinspection ResultOfMethodCallIgnored
- doneFile.delete();
- installTorExecutable();
- installObfs4Executable();
- installSnowflakeExecutable();
- if (!doneFile.createNewFile())
- LOG.warning("Failed to create done file");
- }
-
- protected void extract(InputStream in, File dest) throws IOException {
- OutputStream out = new FileOutputStream(dest);
- copyAndClose(in, out);
- }
-
- protected void installTorExecutable() throws IOException {
- if (LOG.isLoggable(INFO))
- LOG.info("Installing Tor binary for " + architecture);
- File torFile = getTorExecutableFile();
- extract(getExecutableInputStream("tor"), torFile);
- if (!torFile.setExecutable(true, true)) throw new IOException();
- }
-
- protected void installObfs4Executable() throws IOException {
- if (LOG.isLoggable(INFO))
- LOG.info("Installing obfs4proxy binary for " + architecture);
- File obfs4File = getObfs4ExecutableFile();
- extract(getExecutableInputStream("obfs4proxy"), obfs4File);
- if (!obfs4File.setExecutable(true, true)) throw new IOException();
- }
-
- protected void installSnowflakeExecutable() throws IOException {
- if (LOG.isLoggable(INFO))
- LOG.info("Installing snowflake binary for " + architecture);
- File snowflakeFile = getSnowflakeExecutableFile();
- extract(getExecutableInputStream("snowflake"), snowflakeFile);
- if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
- }
-
- private InputStream getExecutableInputStream(String basename) {
- String ext = getExecutableExtension();
- return requireNonNull(resourceProvider
- .getResourceInputStream(architecture + "/" + basename, ext));
- }
-
- protected String getExecutableExtension() {
- return "";
- }
-
- private static void append(StringBuilder strb, String name, Object value) {
- strb.append(name);
- strb.append(" ");
- strb.append(value);
- strb.append("\n");
- }
-
- 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");
- append(strb, "ConnectionPadding", 0);
- String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
- append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
- append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
- String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
- append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
- return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
- }
-
- private void listFiles(File f) {
- if (f.isDirectory()) {
- File[] children = f.listFiles();
- if (children != null) for (File child : children) listFiles(child);
- } else {
- LOG.info(f.getAbsolutePath() + " " + f.length());
- }
- }
-
- private byte[] read(File f) throws IOException {
- byte[] b = new byte[(int) f.length()];
- FileInputStream in = new FileInputStream(f);
- try {
- int offset = 0;
- while (offset < b.length) {
- int read = in.read(b, offset, b.length - offset);
- if (read == -1) throw new EOFException();
- offset += read;
- }
- return b;
- } finally {
- tryToClose(in, LOG, WARNING);
- }
- }
-
- 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,9 +229,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
// Store the port number
- String localPort = String.valueOf(ss.getLocalPort());
+ int localPort = ss.getLocalPort();
Settings s = new Settings();
- s.put(PREF_TOR_PORT, localPort);
+ s.put(PREF_TOR_PORT, String.valueOf(localPort));
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
@@ -483,48 +241,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
}
- private void publishHiddenService(String port) {
- if (!state.isTorRunning()) return;
- String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
- publishV3HiddenService(port, privKey3);
- }
-
- private void publishV3HiddenService(String port, @Nullable String privKey) {
+ private void publishHiddenService(int localPort) {
+ if (!tor.isTorRunning()) return;
+ String privKey = settings.get(HS_PRIVATE_KEY_V3);
LOG.info("Creating v3 hidden service");
- Map portLines = singletonMap(80, "127.0.0.1:" + port);
- Map response;
+ HiddenServiceProperties hsProps;
try {
- // Use the control connection to set up the hidden service
- if (privKey == null) {
- response = controlConnection.addOnion("NEW:ED25519-V3",
- portLines, null);
- } else {
- response = controlConnection.addOnion(privKey, portLines);
- }
+ hsProps = tor.publishHiddenService(localPort, 80, privKey);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
- if (!response.containsKey(HS_ADDRESS)) {
- LOG.warning("Tor did not return a hidden service address");
- return;
- }
- if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
- LOG.warning("Tor did not return a private key");
- return;
- }
- String onion3 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) {
- LOG.info("V3 hidden service " + scrubOnion(onion3));
+ LOG.info("V3 hidden service " + scrubOnion(hsProps.onion));
}
if (privKey == null) {
// Publish the hidden service's onion hostname in transport props
TransportProperties p = new TransportProperties();
- p.put(PROP_ONION_V3, onion3);
+ p.put(PROP_ONION_V3, hsProps.onion);
callback.mergeLocalProperties(p);
// Save the hidden service's private key for next time
Settings s = new Settings();
- s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
+ s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
callback.mergeSettings(s);
}
}
@@ -547,50 +285,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
- protected void enableNetwork(boolean enable) throws IOException {
- if (!state.enableNetwork(enable)) return; // Unchanged
- controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
- }
-
private void enableBridges(List bridgeTypes, String countryCode)
throws IOException {
- if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
if (bridgeTypes.isEmpty()) {
- controlConnection.setConf("UseBridges", "0");
- controlConnection.resetConf(singletonList("Bridge"));
+ tor.disableBridges();
} else {
- Collection conf = new ArrayList<>();
- conf.add("UseBridges 1");
- boolean letsEncrypt = canVerifyLetsEncryptCerts();
+ List bridges = new ArrayList<>();
for (BridgeType bridgeType : bridgeTypes) {
- conf.addAll(circumventionProvider
- .getBridges(bridgeType, countryCode, letsEncrypt));
+ bridges.addAll(circumventionProvider.getBridges(bridgeType,
+ countryCode, canVerifyLetsEncryptCerts));
}
- controlConnection.setConf(conf);
+ tor.enableBridges(bridges);
}
}
- /**
- * Returns true if this device can verify Let's Encrypt certificates signed
- * with the IdentTrust DST Root X3 certificate, which expired at the end of
- * September 2021.
- */
- protected boolean canVerifyLetsEncryptCerts() {
- return true;
- }
-
@Override
public void stop() {
ServerSocket ss = state.setStopped();
tryToClose(ss, LOG, WARNING);
- if (controlSocket != null && controlConnection != null) {
- try {
- LOG.info("Stopping Tor");
- controlConnection.shutdownTor("TERM");
- controlSocket.close();
- } catch (IOException e) {
- logException(LOG, WARNING, e);
- }
+ try {
+ tor.stop();
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
}
}
@@ -701,6 +417,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
TransportProperties remoteProperties = new TransportProperties();
remoteProperties.put(PROP_ONION_V3, remoteOnion);
try {
+ @SuppressWarnings("resource")
ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("127.0.0.1", 0));
int port = ss.getLocalPort();
@@ -717,9 +434,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Rendezvous server socket closed");
}
});
- Map portLines =
- singletonMap(80, "127.0.0.1:" + port);
- controlConnection.addOnion(blob, portLines);
+ tor.publishHiddenService(port, 80, blob);
return new RendezvousEndpoint() {
@Override
@@ -729,8 +444,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void close() throws IOException {
- controlConnection.delOnion(localOnion);
- tryToClose(ss, LOG, WARNING);
+ try {
+ tor.removeHiddenService(localOnion);
+ } finally {
+ tryToClose(ss, LOG, WARNING);
+ }
}
};
} catch (IOException e) {
@@ -739,121 +457,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
- @Override
- public void circuitStatus(String status, String id, String path) {
- // In case of races between receiving CIRCUIT_ESTABLISHED and setting
- // DisableNetwork, set our circuitBuilt flag if not already set
- if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
- LOG.info("Circuit built");
- backoff.reset();
- }
- }
-
- @Override
- public void streamStatus(String status, String id, String target) {
- }
-
- @Override
- public void orConnStatus(String status, String orName) {
- if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
-
- if (status.equals("CONNECTED")) state.onOrConnectionConnected();
- else if (status.equals("CLOSED")) state.onOrConnectionClosed();
- }
-
- @Override
- public void bandwidthUsed(long read, long written) {
- }
-
- @Override
- public void newDescriptors(List orList) {
- }
-
- @Override
- public void message(String severity, String msg) {
- if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
- }
-
- @Override
- public void unrecognized(String type, String msg) {
- if (type.equals("STATUS_CLIENT")) {
- handleClientStatus(removeSeverity(msg));
- } else if (type.equals("STATUS_GENERAL")) {
- handleGeneralStatus(removeSeverity(msg));
- } else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
- String[] parts = msg.split(" ");
- if (parts.length < 2) {
- LOG.warning("Failed to parse HS_DESC UPLOADED event");
- } else if (LOG.isLoggable(INFO)) {
- LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
- }
- }
- }
-
- @Override
- public void controlConnectionClosed() {
- if (state.isTorRunning()) {
- // TODO: Restart the Tor process
- LOG.warning("Control connection closed");
- }
- }
-
- private String removeSeverity(String msg) {
- return msg.replaceFirst("[^ ]+ ", "");
- }
-
- private void handleClientStatus(String msg) {
- if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
- LOG.info("Bootstrapped");
- state.setBootstrapped();
- backoff.reset();
- } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
- if (state.setCircuitBuilt(true)) {
- LOG.info("Circuit built");
- backoff.reset();
- }
- } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
- if (state.setCircuitBuilt(false)) {
- LOG.info("Circuit not built");
- // TODO: Disable and re-enable network to prompt Tor to rebuild
- // its guard/bridge connections? This will also close any
- // established circuits, which might still be functioning
- }
- }
- }
-
- private void handleGeneralStatus(String msg) {
- if (msg.startsWith("CLOCK_JUMPED")) {
- Long time = parseLongArgument(msg, "TIME");
- if (time != null && LOG.isLoggable(WARNING)) {
- LOG.warning("Clock jumped " + time + " seconds");
- }
- } else if (msg.startsWith("CLOCK_SKEW")) {
- Long skew = parseLongArgument(msg, "SKEW");
- if (skew != null && LOG.isLoggable(WARNING)) {
- LOG.warning("Clock is skewed by " + skew + " seconds");
- }
- }
- }
-
- @Nullable
- private Long parseLongArgument(String msg, String argName) {
- String[] args = msg.split(" ");
- for (String arg : args) {
- if (arg.startsWith(argName + "=")) {
- try {
- return Long.parseLong(arg.substring(argName.length() + 1));
- } catch (NumberFormatException e) {
- break;
- }
- }
- }
- if (LOG.isLoggable(WARNING)) {
- LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
- }
- return null;
- }
-
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
@@ -876,7 +479,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
- if (!state.isTorRunning()) return;
+ if (!tor.isTorRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
@@ -960,41 +563,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
if (enableNetwork) {
enableBridges(bridgeTypes, country);
- enableConnectionPadding(enableConnectionPadding);
- enableIpv6(ipv6Only);
+ tor.enableConnectionPadding(enableConnectionPadding);
+ tor.enableIpv6(ipv6Only);
}
- enableNetwork(enableNetwork);
+ tor.enableNetwork(enableNetwork);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
});
}
- private void enableConnectionPadding(boolean enable) throws IOException {
- if (!state.enableConnectionPadding(enable)) return; // Unchanged
- controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
- }
-
- private void enableIpv6(boolean enable) throws IOException {
- if (!state.enableIpv6(enable)) return; // Unchanged
- controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
- controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
- }
-
@ThreadSafe
@NotNullByDefault
- protected class PluginState {
+ private class PluginState {
@GuardedBy("this")
- private boolean started = false,
- stopped = false,
- networkInitialised = false,
- networkEnabled = false,
- paddingEnabled = false,
- ipv6Enabled = false,
- bootstrapped = false,
- circuitBuilt = false,
- settingsChecked = false;
+ private boolean settingsChecked = false;
@GuardedBy("this")
private int reasonsDisabled = 0;
@@ -1003,84 +587,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Nullable
private ServerSocket serverSocket = null;
- @GuardedBy("this")
- private int orConnectionsConnected = 0;
-
- @GuardedBy("this")
- private List bridgeTypes = emptyList();
-
- private synchronized void setStarted() {
- started = true;
- callback.pluginStateChanged(getState());
- }
-
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private synchronized boolean isTorRunning() {
- return started && !stopped;
- }
-
@Nullable
private synchronized ServerSocket setStopped() {
- stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
- callback.pluginStateChanged(getState());
return ss;
}
- private synchronized void setBootstrapped() {
- boolean wasBootstrapped = bootstrapped;
- bootstrapped = true;
- if (!wasBootstrapped) callback.pluginStateChanged(getState());
- }
-
- /**
- * Sets the `circuitBuilt` flag and returns true if the flag has
- * changed.
- */
- private synchronized boolean setCircuitBuilt(boolean built) {
- if (built == circuitBuilt) return false; // Unchanged
- circuitBuilt = built;
- callback.pluginStateChanged(getState());
- return true; // Changed
- }
-
- /**
- * Sets the `networkEnabled` flag and returns true if the flag has
- * changed.
- */
- private synchronized boolean enableNetwork(boolean enable) {
- boolean wasInitialised = networkInitialised;
- boolean wasEnabled = networkEnabled;
- networkInitialised = true;
- networkEnabled = enable;
- if (!enable) circuitBuilt = false;
- if (!wasInitialised || enable != wasEnabled) {
- callback.pluginStateChanged(getState());
- }
- return enable != wasEnabled;
- }
-
- /**
- * Sets the `paddingEnabled` flag and returns true if the flag has
- * changed. Doesn't affect getState().
- */
- private synchronized boolean enableConnectionPadding(boolean enable) {
- if (enable == paddingEnabled) return false; // Unchanged
- paddingEnabled = enable;
- return true; // Changed
- }
-
- /**
- * Sets the `ipv6Enabled` flag and returns true if the flag has
- * changed. Doesn't affect getState().
- */
- private synchronized boolean enableIpv6(boolean enable) {
- if (enable == ipv6Enabled) return false; // Unchanged
- ipv6Enabled = enable;
- return true; // Changed
- }
-
private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked;
settingsChecked = true;
@@ -1093,7 +606,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Doesn't affect getState()
private synchronized boolean setServerSocket(ServerSocket ss) {
- if (stopped || serverSocket != null) return false;
+ if (serverSocket != null || !tor.isTorRunning()) return false;
serverSocket = ss;
return true;
}
@@ -1103,57 +616,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (serverSocket == ss) serverSocket = null;
}
- /**
- * Sets the list of bridge types being used and returns true if the
- * list has changed. The list is empty if bridges are disabled.
- * Doesn't affect getState().
- */
- private synchronized boolean setBridgeTypes(List types) {
- if (types.equals(bridgeTypes)) return false; // Unchanged
- bridgeTypes = types;
- return true; // Changed
+ private synchronized State getState() {
+ return getState(tor.getTorState());
}
- private synchronized State getState() {
- if (!started || stopped || !settingsChecked) {
+ private synchronized State getState(TorState torState) {
+ if (torState == TorState.STARTING_STOPPING || !settingsChecked) {
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
- if (!networkInitialised) return ENABLING;
- if (!networkEnabled) return INACTIVE;
- return bootstrapped && circuitBuilt && orConnectionsConnected > 0
- ? ACTIVE : ENABLING;
+ if (torState == TorState.CONNECTING) return ENABLING;
+ if (torState == TorState.CONNECTED) return ACTIVE;
+ return INACTIVE;
}
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
}
-
- private synchronized void onOrConnectionConnected() {
- int oldConnected = orConnectionsConnected;
- orConnectionsConnected++;
- logOrConnections();
- if (oldConnected == 0) callback.pluginStateChanged(getState());
- }
-
- private synchronized void onOrConnectionClosed() {
- int oldConnected = orConnectionsConnected;
- orConnectionsConnected--;
- if (orConnectionsConnected < 0) {
- LOG.warning("Count was zero before connection closed");
- orConnectionsConnected = 0;
- }
- logOrConnections();
- if (orConnectionsConnected == 0 && oldConnected != 0) {
- callback.pluginStateChanged(getState());
- }
- }
-
- @GuardedBy("this")
- private void logOrConnections() {
- if (LOG.isLoggable(INFO)) {
- LOG.info(orConnectionsConnected + " OR connections connected");
- }
- }
}
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java
index f1efd8825..ce5a902ab 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPluginFactory.java
@@ -3,6 +3,7 @@ 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.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -17,8 +18,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
@@ -45,13 +46,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
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 Executor ioExecutor, eventExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
- protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
protected final Clock clock;
@@ -61,13 +61,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
protected final int torControlPort;
TorPluginFactory(@IoExecutor Executor ioExecutor,
+ @EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
- ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -76,13 +76,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
this.ioExecutor = ioExecutor;
+ this.eventExecutor = eventExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
- this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.clock = clock;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java
new file mode 100644
index 000000000..69c7b43de
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/AbstractTorWrapper.java
@@ -0,0 +1,707 @@
+package org.briarproject.bramble.plugin.tor.wrapper;
+
+import net.freehaven.tor.control.EventHandler;
+import net.freehaven.tor.control.TorControlConnection;
+
+import org.briarproject.nullsafety.InterfaceNotNullByDefault;
+import org.briarproject.nullsafety.NotNullByDefault;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+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.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
+import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.UTF_8;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.copyAndClose;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.scrubOnion;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorUtils.tryToClose;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.CONNECTED;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.CONNECTING;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.DISABLED;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.TorState.STARTING_STOPPING;
+import static org.briarproject.nullsafety.NullSafety.requireNonNull;
+
+@InterfaceNotNullByDefault
+abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
+
+ private static final String[] EVENTS = {
+ "CIRC",
+ "ORCONN",
+ "STATUS_GENERAL",
+ "STATUS_CLIENT",
+ "HS_DESC",
+ "NOTICE",
+ "WARN",
+ "ERR"
+ };
+
+ private static final String OWNER = "__OwningControllerProcess";
+ private static final int COOKIE_TIMEOUT_MS = 3000;
+ private static final int COOKIE_POLLING_INTERVAL_MS = 200;
+ private static final Pattern BOOTSTRAP_PERCENTAGE =
+ Pattern.compile(".*PROGRESS=(\\d{1,3}).*");
+
+ protected final Executor ioExecutor;
+ protected final Executor eventExecutor;
+ private final String architecture;
+ private final File torDirectory, configFile, doneFile, cookieFile;
+ private final int torSocksPort;
+ private final int torControlPort;
+ private final AtomicBoolean used = new AtomicBoolean(false);
+
+ protected final NetworkState state = new NetworkState();
+
+ private volatile Socket controlSocket = null;
+ private volatile TorControlConnection controlConnection = null;
+
+ protected abstract int getProcessId();
+
+ protected abstract long getLastUpdateTime();
+
+ protected abstract InputStream getResourceInputStream(String name,
+ String extension);
+
+ AbstractTorWrapper(Executor ioExecutor,
+ Executor eventExecutor,
+ String architecture,
+ File torDirectory,
+ int torSocksPort,
+ int torControlPort) {
+ this.ioExecutor = ioExecutor;
+ this.eventExecutor = eventExecutor;
+ this.architecture = architecture;
+ this.torDirectory = torDirectory;
+ this.torSocksPort = torSocksPort;
+ this.torControlPort = torControlPort;
+ configFile = new File(torDirectory, "torrc");
+ doneFile = new File(torDirectory, "done");
+ cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
+ }
+
+ protected File getTorExecutableFile() {
+ return new File(torDirectory, "tor");
+ }
+
+ protected File getObfs4ExecutableFile() {
+ return new File(torDirectory, "obfs4proxy");
+ }
+
+ protected File getSnowflakeExecutableFile() {
+ return new File(torDirectory, "snowflake");
+ }
+
+ @Override
+ public void setObserver(@Nullable Observer observer) {
+ state.setObserver(observer);
+ }
+
+ @Override
+ public void start() throws IOException, InterruptedException {
+ if (used.getAndSet(true)) throw new IllegalStateException();
+ if (!torDirectory.exists()) {
+ if (!torDirectory.mkdirs()) {
+ throw new IOException("Could not create Tor directory");
+ }
+ }
+ // Install or update the assets if necessary
+ if (!assetsAreUpToDate()) installAssets();
+ // Start from the default config every time
+ extract(getConfigInputStream(), configFile);
+ if (cookieFile.exists() && !cookieFile.delete())
+ LOG.warning("Old auth cookie not deleted");
+ // Start a new Tor process
+ LOG.info("Starting Tor");
+ File torFile = getTorExecutableFile();
+ String torPath = torFile.getAbsolutePath();
+ String configPath = configFile.getAbsolutePath();
+ String pid = String.valueOf(getProcessId());
+ Process torProcess;
+ ProcessBuilder pb =
+ new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
+ Map env = pb.environment();
+ env.put("HOME", torDirectory.getAbsolutePath());
+ pb.directory(torDirectory);
+ pb.redirectErrorStream(true);
+ try {
+ torProcess = pb.start();
+ } catch (SecurityException e) {
+ throw new IOException(e);
+ }
+ // Wait for the Tor process to start
+ waitForTorToStart(torProcess);
+ // Wait for the auth cookie file to be created/updated
+ long start = System.currentTimeMillis();
+ while (cookieFile.length() < 32) {
+ if (System.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
+ throw new IOException("Auth cookie not created");
+ }
+ //noinspection BusyWait
+ Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
+ }
+ LOG.info("Auth cookie created");
+ // Open a control connection and authenticate using the cookie file
+ controlSocket = new Socket("127.0.0.1", torControlPort);
+ controlConnection = new TorControlConnection(controlSocket);
+ controlConnection.authenticate(read(cookieFile));
+ // Tell Tor to exit when the control connection is closed
+ controlConnection.takeOwnership();
+ controlConnection.resetConf(singletonList(OWNER));
+ // Register to receive events from the Tor process
+ controlConnection.setEventHandler(this);
+ controlConnection.setEvents(asList(EVENTS));
+ // Check whether Tor has already bootstrapped
+ String info = controlConnection.getInfo("status/bootstrap-phase");
+ if (info != null && info.contains("PROGRESS=")) {
+ int percentage = parseBootstrapPercentage(info);
+ if (percentage == 100) LOG.info("Tor has already bootstrapped");
+ state.setBootstrapPercentage(percentage);
+ }
+ // Check whether Tor has already built a circuit
+ info = controlConnection.getInfo("status/circuit-established");
+ if ("1".equals(info)) {
+ LOG.info("Tor has already built a circuit");
+ state.setCircuitBuilt(true);
+ }
+ state.setStarted();
+ }
+
+ private boolean assetsAreUpToDate() {
+ return doneFile.lastModified() > getLastUpdateTime();
+ }
+
+ private void installAssets() throws IOException {
+ // The done file may already exist from a previous installation
+ //noinspection ResultOfMethodCallIgnored
+ doneFile.delete();
+ installTorExecutable();
+ installObfs4Executable();
+ installSnowflakeExecutable();
+ extract(getConfigInputStream(), configFile);
+ if (!doneFile.createNewFile()) {
+ LOG.warning("Failed to create done file");
+ }
+ }
+
+ protected void extract(InputStream in, File dest) throws IOException {
+ @SuppressWarnings("IOStreamConstructor")
+ OutputStream out = new FileOutputStream(dest);
+ copyAndClose(in, out);
+ }
+
+ protected void installTorExecutable() throws IOException {
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("Installing Tor binary for " + architecture);
+ }
+ File torFile = getTorExecutableFile();
+ extract(getExecutableInputStream("tor"), torFile);
+ if (!torFile.setExecutable(true, true)) throw new IOException();
+ }
+
+ protected void installObfs4Executable() throws IOException {
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("Installing obfs4proxy binary for " + architecture);
+ }
+ File obfs4File = getObfs4ExecutableFile();
+ extract(getExecutableInputStream("obfs4proxy"), obfs4File);
+ if (!obfs4File.setExecutable(true, true)) throw new IOException();
+ }
+
+ protected void installSnowflakeExecutable() throws IOException {
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("Installing snowflake binary for " + architecture);
+ }
+ File snowflakeFile = getSnowflakeExecutableFile();
+ extract(getExecutableInputStream("snowflake"), snowflakeFile);
+ if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
+ }
+
+ private InputStream getExecutableInputStream(String basename) {
+ String ext = getExecutableExtension();
+ return requireNonNull(
+ getResourceInputStream(architecture + "/" + basename, ext));
+ }
+
+ protected String getExecutableExtension() {
+ return "";
+ }
+
+ private static void append(StringBuilder strb, String name, Object value) {
+ strb.append(name);
+ strb.append(" ");
+ strb.append(value);
+ strb.append("\n");
+ }
+
+ 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");
+ append(strb, "ConnectionPadding", 0);
+ String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
+ append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
+ append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
+ String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
+ append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
+ return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
+ }
+
+ private byte[] read(File f) throws IOException {
+ byte[] b = new byte[(int) f.length()];
+ FileInputStream in = new FileInputStream(f);
+ try {
+ int offset = 0;
+ while (offset < b.length) {
+ int read = in.read(b, offset, b.length - offset);
+ if (read == -1) throw new EOFException();
+ offset += read;
+ }
+ return b;
+ } finally {
+ tryToClose(in, LOG, WARNING);
+ }
+ }
+
+ protected void waitForTorToStart(Process torProcess)
+ throws InterruptedException, IOException {
+ 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) throw new IOException("Tor exited with value " + exit);
+ }
+
+ @Override
+ public HiddenServiceProperties publishHiddenService(int localPort,
+ int remotePort, @Nullable String privKey) throws IOException {
+ Map portLines =
+ singletonMap(remotePort, "127.0.0.1:" + localPort);
+ // Use the control connection to set up the hidden service
+ Map response;
+ if (privKey == null) {
+ response = getControlConnection().addOnion("NEW:ED25519-V3",
+ portLines, null);
+ } else {
+ response = getControlConnection().addOnion(privKey, portLines);
+ }
+ if (!response.containsKey(HS_ADDRESS)) {
+ throw new IOException("Missing hidden service address");
+ }
+ if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
+ throw new IOException("Missing private key");
+ }
+ String onion = response.get(HS_ADDRESS);
+ if (privKey == null) privKey = response.get(HS_PRIVKEY);
+ return new HiddenServiceProperties(onion, privKey);
+ }
+
+ @Override
+ public void removeHiddenService(String onion) throws IOException {
+ getControlConnection().delOnion(onion);
+ }
+
+ @Override
+ public void enableNetwork(boolean enable) throws IOException {
+ if (!state.enableNetwork(enable)) return; // Unchanged
+ getControlConnection().setConf("DisableNetwork", enable ? "0" : "1");
+ }
+
+ @Override
+ public void enableBridges(List bridges) throws IOException {
+ if (!state.setBridges(bridges)) return; // Unchanged
+ List conf = new ArrayList<>(bridges.size() + 1);
+ conf.add("UseBridges 1");
+ conf.addAll(bridges);
+ getControlConnection().setConf(conf);
+ }
+
+ @Override
+ public void disableBridges() throws IOException {
+ if (!state.setBridges(emptyList())) return; // Unchanged
+ getControlConnection().setConf("UseBridges", "0");
+ }
+
+ @Override
+ public void stop() throws IOException {
+ state.setStopped();
+ if (controlSocket != null && controlConnection != null) {
+ LOG.info("Stopping Tor");
+ try {
+ controlConnection.shutdownTor("TERM");
+ } finally {
+ tryToClose(controlSocket, LOG, WARNING);
+ }
+ }
+ }
+
+ @Override
+ public void circuitStatus(String status, String id, String path) {
+ // In case of races between receiving CIRCUIT_ESTABLISHED and setting
+ // DisableNetwork, set our circuitBuilt flag if not already set
+ if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
+ LOG.info("Circuit built");
+ }
+ }
+
+ @Override
+ public void streamStatus(String status, String id, String target) {
+ }
+
+ @Override
+ public void orConnStatus(String status, String orName) {
+ if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
+
+ if (status.equals("CONNECTED")) state.onOrConnectionConnected();
+ else if (status.equals("CLOSED")) state.onOrConnectionClosed();
+ }
+
+ @Override
+ public void bandwidthUsed(long read, long written) {
+ }
+
+ @Override
+ public void newDescriptors(List orList) {
+ }
+
+ @Override
+ public void message(String severity, String msg) {
+ if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
+ }
+
+ @Override
+ public void unrecognized(String type, String msg) {
+ if (type.equals("STATUS_CLIENT")) {
+ handleClientStatus(removeSeverity(msg));
+ } else if (type.equals("STATUS_GENERAL")) {
+ handleGeneralStatus(removeSeverity(msg));
+ } else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
+ String[] parts = msg.split(" ");
+ if (parts.length < 2) {
+ LOG.warning("Failed to parse HS_DESC UPLOADED event");
+ } else if (LOG.isLoggable(INFO)) {
+ String onion = parts[1];
+ LOG.info("V3 descriptor uploaded for " + scrubOnion(onion));
+ state.onHsDescriptorUploaded(onion);
+ }
+ }
+ }
+
+ private String removeSeverity(String msg) {
+ return msg.replaceFirst("[^ ]+ ", "");
+ }
+
+ private void handleClientStatus(String msg) {
+ if (msg.startsWith("BOOTSTRAP PROGRESS=")) {
+ int percentage = parseBootstrapPercentage(msg);
+ if (percentage == 100) LOG.info("Bootstrapped");
+ state.setBootstrapPercentage(percentage);
+ } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
+ if (state.setCircuitBuilt(true)) LOG.info("Circuit built");
+ } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
+ if (state.setCircuitBuilt(false)) {
+ LOG.info("Circuit not built");
+ // TODO: Disable and re-enable network to prompt Tor to rebuild
+ // its guard/bridge connections? This will also close any
+ // established circuits, which might still be functioning
+ }
+ }
+ }
+
+ private int parseBootstrapPercentage(String s) {
+ Matcher matcher = BOOTSTRAP_PERCENTAGE.matcher(s);
+ if (matcher.matches()) {
+ try {
+ return Integer.parseInt(matcher.group(1));
+ } catch (NumberFormatException e) {
+ // Fall through
+ }
+ }
+ if (LOG.isLoggable(WARNING)) {
+ LOG.warning("Failed to parse bootstrap percentage: " + s);
+ }
+ return 0;
+ }
+
+ private void handleGeneralStatus(String msg) {
+ if (msg.startsWith("CLOCK_JUMPED")) {
+ Long time = parseLongArgument(msg, "TIME");
+ if (time != null && LOG.isLoggable(WARNING)) {
+ LOG.warning("Clock jumped " + time + " seconds");
+ }
+ } else if (msg.startsWith("CLOCK_SKEW")) {
+ Long skew = parseLongArgument(msg, "SKEW");
+ if (skew != null && LOG.isLoggable(WARNING)) {
+ LOG.warning("Clock is skewed by " + skew + " seconds");
+ }
+ }
+ }
+
+ @Nullable
+ private Long parseLongArgument(String msg, String argName) {
+ String[] args = msg.split(" ");
+ for (String arg : args) {
+ if (arg.startsWith(argName + "=")) {
+ try {
+ return Long.parseLong(arg.substring(argName.length() + 1));
+ } catch (NumberFormatException e) {
+ break;
+ }
+ }
+ }
+ if (LOG.isLoggable(WARNING)) {
+ LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
+ }
+ return null;
+ }
+
+ @Override
+ public void controlConnectionClosed() {
+ if (state.isTorRunning()) {
+ // TODO: Restart the Tor process
+ LOG.warning("Control connection closed");
+ }
+ }
+
+ @Override
+ public void enableConnectionPadding(boolean enable) throws IOException {
+ if (!state.enableConnectionPadding(enable)) return; // Unchanged
+ getControlConnection().setConf("ConnectionPadding", enable ? "1" : "0");
+ }
+
+ @Override
+ public void enableIpv6(boolean enable) throws IOException {
+ if (!state.enableIpv6(enable)) return; // Unchanged
+ getControlConnection().setConf("ClientUseIPv4", enable ? "0" : "1");
+ getControlConnection().setConf("ClientUseIPv6", enable ? "1" : "0");
+ }
+
+ @Override
+ public TorState getTorState() {
+ return state.getState();
+ }
+
+ @Override
+ public boolean isTorRunning() {
+ return state.isTorRunning();
+ }
+
+ private TorControlConnection getControlConnection() throws IOException {
+ TorControlConnection controlConnection = this.controlConnection;
+ if (controlConnection == null) {
+ throw new IOException("Control connection not opened");
+ }
+ return controlConnection;
+ }
+
+ @ThreadSafe
+ @NotNullByDefault
+ private class NetworkState {
+
+ @GuardedBy("this")
+ @Nullable
+ private Observer observer = null;
+
+ @GuardedBy("this")
+ private boolean started = false,
+ stopped = false,
+ networkInitialised = false,
+ networkEnabled = false,
+ paddingEnabled = false,
+ ipv6Enabled = false,
+ circuitBuilt = false;
+
+ @GuardedBy("this")
+ private int bootstrapPercentage = 0;
+
+ @GuardedBy("this")
+ private List bridges = emptyList();
+
+ @GuardedBy("this")
+ private int orConnectionsConnected = 0;
+
+ @GuardedBy("this")
+ @Nullable
+ private TorState state = null;
+
+ private synchronized void setObserver(
+ @Nullable Observer observer) {
+ this.observer = observer;
+ }
+
+ @GuardedBy("this")
+ private void updateState() {
+ TorState newState = getState();
+ if (newState != state) {
+ state = newState;
+ if (observer != null) {
+ // Notify the observer on the event executor
+ eventExecutor.execute(() -> observer.onState(newState));
+ }
+ }
+ }
+
+ private synchronized void setStarted() {
+ started = true;
+ updateState();
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private synchronized boolean isTorRunning() {
+ return started && !stopped;
+ }
+
+ private synchronized void setStopped() {
+ stopped = true;
+ updateState();
+ }
+
+ private synchronized void setBootstrapPercentage(int percentage) {
+ if (percentage == bootstrapPercentage) return;
+ bootstrapPercentage = percentage;
+ if (observer != null) {
+ // Notify the observer on the event executor
+ eventExecutor.execute(() ->
+ observer.onBootstrapPercentage(percentage));
+ }
+ updateState();
+ }
+
+ /**
+ * Sets the `circuitBuilt` flag and returns true if the flag has
+ * changed.
+ */
+ private synchronized boolean setCircuitBuilt(boolean built) {
+ if (built == circuitBuilt) return false; // Unchanged
+ circuitBuilt = built;
+ updateState();
+ return true; // Changed
+ }
+
+ /**
+ * Sets the `networkEnabled` flag and returns true if the flag has
+ * changed.
+ */
+ private synchronized boolean enableNetwork(boolean enable) {
+ boolean wasInitialised = networkInitialised;
+ boolean wasEnabled = networkEnabled;
+ networkInitialised = true;
+ networkEnabled = enable;
+ if (!enable) circuitBuilt = false;
+ if (!wasInitialised || enable != wasEnabled) {
+ updateState();
+ }
+ return enable != wasEnabled;
+ }
+
+ /**
+ * Sets the `paddingEnabled` flag and returns true if the flag has
+ * changed. Doesn't affect getState().
+ */
+ private synchronized boolean enableConnectionPadding(boolean enable) {
+ if (enable == paddingEnabled) return false; // Unchanged
+ paddingEnabled = enable;
+ return true; // Changed
+ }
+
+ /**
+ * Sets the `ipv6Enabled` flag and returns true if the flag has
+ * changed. Doesn't affect getState().
+ */
+ private synchronized boolean enableIpv6(boolean enable) {
+ if (enable == ipv6Enabled) return false; // Unchanged
+ ipv6Enabled = enable;
+ return true; // Changed
+ }
+
+ /**
+ * Sets the list of bridges being used and returns true if the
+ * list has changed. The list is empty if bridges are disabled.
+ * Doesn't affect getState().
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private synchronized boolean setBridges(List bridges) {
+ if (this.bridges.equals(bridges)) return false; // Unchanged
+ this.bridges = bridges;
+ return true; // Changed
+ }
+
+ private synchronized TorState getState() {
+ if (!started || stopped) return STARTING_STOPPING;
+ if (!networkInitialised) return CONNECTING;
+ if (!networkEnabled) return DISABLED;
+ return bootstrapPercentage == 100 && circuitBuilt
+ && orConnectionsConnected > 0 ? CONNECTED : CONNECTING;
+ }
+
+ private synchronized void onOrConnectionConnected() {
+ int oldConnected = orConnectionsConnected;
+ orConnectionsConnected++;
+ logOrConnections();
+ if (oldConnected == 0) updateState();
+ }
+
+ private synchronized void onOrConnectionClosed() {
+ int oldConnected = orConnectionsConnected;
+ orConnectionsConnected--;
+ if (orConnectionsConnected < 0) {
+ LOG.warning("Count was zero before connection closed");
+ orConnectionsConnected = 0;
+ }
+ logOrConnections();
+ if (orConnectionsConnected == 0 && oldConnected != 0) {
+ updateState();
+ }
+ }
+
+ @GuardedBy("this")
+ private void logOrConnections() {
+ if (LOG.isLoggable(INFO)) {
+ LOG.info(orConnectionsConnected + " OR connections connected");
+ }
+ }
+
+ private synchronized void onHsDescriptorUploaded(String onion) {
+ if (observer != null) {
+ // Notify the observer on the event executor
+ eventExecutor.execute(() ->
+ observer.onHsDescriptorUpload(onion));
+ }
+ }
+ }
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionModule.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionModule.java
similarity index 83%
rename from bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionModule.java
rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionModule.java
index 9ad744861..be4249f48 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionModule.java
@@ -1,4 +1,4 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
import javax.inject.Singleton;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProvider.java
similarity index 94%
rename from bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProvider.java
index ef8cbb7cc..ac0ee85d0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProvider.java
@@ -1,10 +1,13 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+@ThreadSafe
@NotNullByDefault
public interface CircumventionProvider {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImpl.java
similarity index 87%
rename from bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
rename to bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImpl.java
index 81e6eee02..a5a56df8a 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImpl.java
@@ -1,4 +1,4 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
@@ -16,11 +16,11 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@Immutable
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorUtils.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorUtils.java
new file mode 100644
index 000000000..d83d3feb9
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorUtils.java
@@ -0,0 +1,66 @@
+package org.briarproject.bramble.plugin.tor.wrapper;
+
+import org.briarproject.nullsafety.NotNullByDefault;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.Charset;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.plugin.tor.wrapper.TorWrapper.LOG;
+
+@NotNullByDefault
+class TorUtils {
+
+ @SuppressWarnings("CharsetObjectCanBeUsed")
+ static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ static String scrubOnion(String onion) {
+ // Keep first three characters of onion address
+ return onion.substring(0, 3) + "[scrubbed]";
+ }
+
+ static void copyAndClose(InputStream in, OutputStream out) {
+ byte[] buf = new byte[4096];
+ try {
+ while (true) {
+ int read = in.read(buf);
+ if (read == -1) break;
+ out.write(buf, 0, read);
+ }
+ in.close();
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ tryToClose(in, LOG, WARNING);
+ tryToClose(out, LOG, WARNING);
+ }
+ }
+
+ static void tryToClose(@Nullable Closeable c, Logger logger, Level level) {
+ try {
+ if (c != null) c.close();
+ } catch (IOException e) {
+ logException(logger, level, e);
+ }
+ }
+
+ static void tryToClose(@Nullable Socket s, Logger logger, Level level) {
+ try {
+ if (s != null) s.close();
+ } catch (IOException e) {
+ logException(logger, level, e);
+ }
+ }
+
+ private static void logException(Logger logger, Level level, Throwable t) {
+ if (logger.isLoggable(level)) logger.log(level, t.toString(), t);
+ }
+}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java
new file mode 100644
index 000000000..01a94fe23
--- /dev/null
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/TorWrapper.java
@@ -0,0 +1,162 @@
+package org.briarproject.bramble.plugin.tor.wrapper;
+
+import org.briarproject.nullsafety.NotNullByDefault;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+import static java.util.logging.Logger.getLogger;
+
+@NotNullByDefault
+public interface TorWrapper {
+
+ Logger LOG = getLogger(TorWrapper.class.getName());
+
+ /**
+ * Starts the Tor process.
+ *
+ * This method must only be called once. To restart the Tor process, stop
+ * this wrapper instance and then create a new instance.
+ */
+ void start() throws IOException, InterruptedException;
+
+ /**
+ * Tell the Tor process to stop and returns without waiting for the
+ * process to exit.
+ */
+ void stop() throws IOException;
+
+ /**
+ * Sets an observer for observing the state of the wrapper, replacing any
+ * existing observer, or removes any existing observer if the argument is
+ * null.
+ */
+ void setObserver(@Nullable Observer observer);
+
+ /**
+ * Returns the current state of the wrapper.
+ */
+ TorState getTorState();
+
+ /**
+ * Returns true if the wrapper has been {@link #start() started} and not
+ * yet {@link #stop()} stopped.
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ boolean isTorRunning();
+
+ /**
+ * Publishes an ephemeral hidden service.
+ *
+ * @param localPort The local port on which the service is listening.
+ * @param remotePort The port number that clients of the service will see.
+ * @param privateKey The private key of the hidden service, in the form
+ * returned by a previous call to this method, or null if a new service
+ * should be created.
+ */
+ HiddenServiceProperties publishHiddenService(int localPort,
+ int remotePort, @Nullable String privateKey) throws IOException;
+
+ /**
+ * Removes (unpublishes) an ephemeral hidden service that was created by
+ * calling {@link #publishHiddenService(int, int, String)}.
+ */
+ void removeHiddenService(String onion) throws IOException;
+
+ /**
+ * Enables or disables the Tor process's network connection. The network
+ * connection is disabled by default.
+ */
+ void enableNetwork(boolean enable) throws IOException;
+
+ /**
+ * Configures Tor to use the given list of bridges for connecting to the
+ * Tor network. Bridges are not used by default.
+ *
+ * Each item in the list should be a bridge line in the same
+ * format that would be used in a torrc file (including the Bridge keyword).
+ */
+ void enableBridges(List bridges) throws IOException;
+
+ /**
+ * Configures Tor not to use bridges for connecting to the Tor network.
+ * Bridges are not used by default.
+ */
+ void disableBridges() throws IOException;
+
+ /**
+ * Enables or disables connection padding. Padding is disabled by default.
+ */
+ void enableConnectionPadding(boolean enable) throws IOException;
+
+ /**
+ * Configures Tor to use IPv6 or IPv4 for connecting to the Tor network.
+ * IPv4 is used by default.
+ */
+ void enableIpv6(boolean ipv6Only) throws IOException;
+
+ /**
+ * The state of the Tor wrapper.
+ */
+ enum TorState {
+
+ /**
+ * The Tor process is either starting or stopping.
+ */
+ STARTING_STOPPING,
+
+ /**
+ * The Tor process has started, its network connection is enabled, and
+ * it is connecting (or reconnecting) to the Tor network.
+ */
+ CONNECTING,
+
+ /**
+ * The Tor process has started, its network connection is enabled, and
+ * it has connected to the Tor network. In this state it should be
+ * possible to make connections via the SOCKS port.
+ */
+ CONNECTED,
+
+ /**
+ * The Tor process has started but its network connection is disabled.
+ */
+ DISABLED
+ }
+
+ /**
+ * An interface for observing changes to the {@link TorState state} of the
+ * Tor process. All calls happen on the event executor supplied to the
+ * wrapper's constructor.
+ */
+ interface Observer {
+
+ /**
+ * Called whenever the state of the Tor process changes.
+ */
+ void onState(TorState s);
+
+ /**
+ * Called whenever the bootstrap percentage changes.
+ */
+ void onBootstrapPercentage(int percentage);
+
+ /**
+ * Called whenever a hidden service descriptor is uploaded.
+ */
+ void onHsDescriptorUpload(String onion);
+ }
+
+ class HiddenServiceProperties {
+
+ public final String onion, privKey;
+
+ HiddenServiceProperties(String onion, String privKey) {
+ this.onion = onion;
+ this.privKey = privKey;
+ }
+ }
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImplTest.java
similarity index 72%
rename from bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImplTest.java
rename to bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImplTest.java
index 564efcded..374116e45 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/wrapper/CircumventionProviderImplTest.java
@@ -1,4 +1,4 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
@@ -7,16 +7,16 @@ import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
-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;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
-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.DPI_BRIDGES;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BLOCKED;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BRIDGES;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.VANILLA;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.DEFAULT_BRIDGES;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.DPI_BRIDGES;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.NON_DEFAULT_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/BrambleJavaModule.java b/bramble-java/src/main/java/org/briarproject/bramble/BrambleJavaModule.java
index d627025ab..2d4fd9376 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/BrambleJavaModule.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/BrambleJavaModule.java
@@ -3,7 +3,7 @@ package org.briarproject.bramble;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.mailbox.ModularMailboxModule;
import org.briarproject.bramble.network.JavaNetworkModule;
-import org.briarproject.bramble.plugin.tor.CircumventionModule;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.JavaSystemModule;
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java
deleted file mode 100644
index 5dd647ead..000000000
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java
+++ /dev/null
@@ -1,62 +0,0 @@
-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.plugin.Backoff;
-import org.briarproject.bramble.api.plugin.PluginCallback;
-import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.api.system.ResourceProvider;
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.CodeSource;
-import java.util.concurrent.Executor;
-
-import javax.net.SocketFactory;
-
-@NotNullByDefault
-abstract class JavaTorPlugin extends TorPlugin {
-
- JavaTorPlugin(Executor ioExecutor,
- Executor wakefulIoExecutor,
- NetworkManager networkManager,
- LocationUtils locationUtils,
- SocketFactory torSocketFactory,
- Clock clock,
- ResourceProvider resourceProvider,
- CircumventionProvider circumventionProvider,
- BatteryManager batteryManager,
- Backoff backoff,
- 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, backoff,
- torRendezvousCrypto, callback, architecture,
- maxLatency, maxIdleTime, torDirectory, torSocksPort,
- torControlPort);
- }
-
- @Override
- protected long getLastUpdateTime() {
- CodeSource codeSource =
- getClass().getProtectionDomain().getCodeSource();
- if (codeSource == null) throw new AssertionError("CodeSource null");
- try {
- URI path = codeSource.getLocation().toURI();
- File file = new File(path);
- return file.lastModified();
- } catch (URISyntaxException e) {
- throw new AssertionError(e);
- }
- }
-}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java
deleted file mode 100644
index 90de04434..000000000
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.briarproject.bramble.plugin.tor;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-
-import org.briarproject.bramble.api.battery.BatteryManager;
-import org.briarproject.bramble.api.network.NetworkManager;
-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;
-import org.briarproject.bramble.api.system.ResourceProvider;
-import org.briarproject.nullsafety.NotNullByDefault;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-import javax.net.SocketFactory;
-
-@NotNullByDefault
-class UnixTorPlugin extends JavaTorPlugin {
-
- UnixTorPlugin(Executor ioExecutor,
- Executor wakefulIoExecutor,
- NetworkManager networkManager,
- LocationUtils locationUtils,
- SocketFactory torSocketFactory,
- Clock clock,
- ResourceProvider resourceProvider,
- CircumventionProvider circumventionProvider,
- BatteryManager batteryManager,
- Backoff backoff,
- 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, backoff,
- torRendezvousCrypto, callback, architecture,
- maxLatency, maxIdleTime, torDirectory, torSocksPort,
- torControlPort);
- }
-
- @Override
- protected int getProcessId() {
- return CLibrary.INSTANCE.getpid();
- }
-
- private interface CLibrary extends Library {
-
- CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
-
- int getpid();
- }
-}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
index bfc86cdbf..a9b48f1a2 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
@@ -3,6 +3,7 @@ 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.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -13,8 +14,10 @@ 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 org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
+import org.briarproject.bramble.plugin.tor.wrapper.UnixTorWrapper;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
@@ -34,13 +37,13 @@ public class UnixTorPluginFactory extends TorPluginFactory {
@Inject
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
+ @EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
- ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -48,8 +51,8 @@ public class UnixTorPluginFactory extends TorPluginFactory {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
- super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
- eventBus, torSocketFactory, backoffFactory, resourceProvider,
+ super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
+ locationUtils, eventBus, torSocketFactory, backoffFactory,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@@ -62,6 +65,7 @@ public class UnixTorPluginFactory extends TorPluginFactory {
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
+ //noinspection IfCanBeSwitch
if (arch.equals("amd64")) return "x86_64";
else if (arch.equals("aarch64")) return "aarch64";
else if (arch.equals("arm")) return "armhf";
@@ -72,11 +76,11 @@ public class UnixTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
- return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
- networkManager, locationUtils, torSocketFactory, clock,
- resourceProvider, circumventionProvider, batteryManager,
- backoff, torRendezvousCrypto, callback, architecture,
- MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
- torControlPort);
+ TorWrapper tor = new UnixTorWrapper(ioExecutor, eventExecutor,
+ architecture, torDirectory, torSocksPort, torControlPort);
+ return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
+ locationUtils, torSocketFactory, circumventionProvider,
+ batteryManager, backoff, torRendezvousCrypto, tor, callback,
+ MAX_LATENCY, MAX_IDLE_TIME, true);
}
}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPluginFactory.java
index cf4f5a3c4..23ba5ba56 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPluginFactory.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPluginFactory.java
@@ -3,6 +3,7 @@ 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.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -13,8 +14,10 @@ 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 org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.TorWrapper;
+import org.briarproject.bramble.plugin.tor.wrapper.WindowsTorWrapper;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
@@ -34,13 +37,13 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
@Inject
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
+ @EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
- ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -48,8 +51,8 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
- super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
- eventBus, torSocketFactory, backoffFactory, resourceProvider,
+ super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
+ locationUtils, eventBus, torSocketFactory, backoffFactory,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@@ -70,11 +73,11 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
- return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
- networkManager, locationUtils, torSocketFactory, clock,
- resourceProvider, circumventionProvider, batteryManager,
- backoff, torRendezvousCrypto, callback, architecture,
- MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
- torControlPort);
+ TorWrapper tor = new WindowsTorWrapper(ioExecutor, eventExecutor,
+ architecture, torDirectory, torSocksPort, torControlPort);
+ return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
+ locationUtils, torSocketFactory, circumventionProvider,
+ batteryManager, backoff, torRendezvousCrypto, tor, callback,
+ MAX_LATENCY, MAX_IDLE_TIME, true);
}
}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/JavaTorWrapper.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/JavaTorWrapper.java
new file mode 100644
index 000000000..9d8ff3e9b
--- /dev/null
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/JavaTorWrapper.java
@@ -0,0 +1,47 @@
+package org.briarproject.bramble.plugin.tor.wrapper;
+
+import org.briarproject.nullsafety.NotNullByDefault;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.CodeSource;
+import java.util.concurrent.Executor;
+
+import static org.briarproject.nullsafety.NullSafety.requireNonNull;
+
+@NotNullByDefault
+abstract class JavaTorWrapper extends AbstractTorWrapper {
+
+ JavaTorWrapper(Executor ioExecutor,
+ Executor eventExecutor,
+ String architecture,
+ File torDirectory,
+ int torSocksPort,
+ int torControlPort) {
+ super(ioExecutor, eventExecutor, architecture, torDirectory,
+ torSocksPort, torControlPort);
+ }
+
+ @Override
+ protected long getLastUpdateTime() {
+ CodeSource codeSource =
+ getClass().getProtectionDomain().getCodeSource();
+ if (codeSource == null) throw new AssertionError("CodeSource null");
+ try {
+ URI path = codeSource.getLocation().toURI();
+ File file = new File(path);
+ return file.lastModified();
+ } catch (URISyntaxException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ protected InputStream getResourceInputStream(String name,
+ String extension) {
+ ClassLoader cl = getClass().getClassLoader();
+ return requireNonNull(cl.getResourceAsStream(name + extension));
+ }
+}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java
new file mode 100644
index 000000000..d6f599c78
--- /dev/null
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/UnixTorWrapper.java
@@ -0,0 +1,53 @@
+package org.briarproject.bramble.plugin.tor.wrapper;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+
+import org.briarproject.nullsafety.NotNullByDefault;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+
+/**
+ * A Tor wrapper for Unix-like operating systems.
+ */
+@NotNullByDefault
+public class UnixTorWrapper extends JavaTorWrapper {
+
+ /**
+ * @param ioExecutor The wrapper will use this executor to run IO tasks,
+ * some of which may run for the lifetime of the wrapper, so the executor
+ * should have an unlimited thread pool.
+ * @param eventExecutor The wrapper will use this executor to call the
+ * {@link Observer observer} (if any). To ensure that events are observed
+ * in the order they occur, this executor should have a single thread (eg
+ * the app's main thread).
+ * @param architecture The processor architecture of the Tor and pluggable
+ * transport binaries.
+ * @param torDirectory The directory where the Tor process should keep its
+ * state.
+ * @param torSocksPort The port number to use for Tor's SOCKS port.
+ * @param torControlPort The port number to use for Tor's control port.
+ */
+ public UnixTorWrapper(Executor ioExecutor,
+ Executor eventExecutor,
+ String architecture,
+ File torDirectory,
+ int torSocksPort,
+ int torControlPort) {
+ super(ioExecutor, eventExecutor, architecture, torDirectory,
+ torSocksPort, torControlPort);
+ }
+
+ @Override
+ protected int getProcessId() {
+ return CLibrary.INSTANCE.getpid();
+ }
+
+ private interface CLibrary extends Library {
+
+ CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
+
+ int getpid();
+ }
+}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java
similarity index 58%
rename from bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java
rename to bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java
index c5fa0013d..3807786b5 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/WindowsTorPlugin.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/wrapper/WindowsTorWrapper.java
@@ -1,54 +1,48 @@
-package org.briarproject.bramble.plugin.tor;
+package org.briarproject.bramble.plugin.tor.wrapper;
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.plugin.Backoff;
-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 org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
+import java.io.IOException;
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;
+/**
+ * A Tor wrapper for the Windows operating system.
+ */
@NotNullByDefault
-class WindowsTorPlugin extends JavaTorPlugin {
+public class WindowsTorWrapper extends JavaTorWrapper {
- WindowsTorPlugin(Executor ioExecutor,
- Executor wakefulIoExecutor,
- NetworkManager networkManager,
- LocationUtils locationUtils,
- SocketFactory torSocketFactory,
- Clock clock,
- ResourceProvider resourceProvider,
- CircumventionProvider circumventionProvider,
- BatteryManager batteryManager,
- Backoff backoff,
- TorRendezvousCrypto torRendezvousCrypto,
- PluginCallback callback,
+ /**
+ * @param ioExecutor The wrapper will use this executor to run IO tasks,
+ * some of which may run for the lifetime of the wrapper, so the executor
+ * should have an unlimited thread pool.
+ * @param eventExecutor The wrapper will use this executor to call
+ * @param eventExecutor The wrapper will use this executor to call the
+ * {@link Observer observer} (if any). To ensure that events are observed
+ * in the order they occur, this executor should have a single thread (eg
+ * the app's main thread).
+ * @param architecture The processor architecture of the Tor and pluggable
+ * transport binaries.
+ * @param torDirectory The directory where the Tor process should keep its
+ * state.
+ * @param torSocksPort The port number to use for Tor's SOCKS port.
+ * @param torControlPort The port number to use for Tor's control port.
+ */
+ public WindowsTorWrapper(Executor ioExecutor,
+ Executor eventExecutor,
String architecture,
- long maxLatency,
- int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
- super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
- torSocketFactory, clock, resourceProvider,
- circumventionProvider, batteryManager, backoff,
- torRendezvousCrypto, callback, architecture,
- maxLatency, maxIdleTime, torDirectory, torSocksPort,
- torControlPort);
+ super(ioExecutor, eventExecutor, architecture, torDirectory,
+ torSocksPort, torControlPort);
}
@Override
@@ -58,7 +52,7 @@ class WindowsTorPlugin extends JavaTorPlugin {
@Override
protected void waitForTorToStart(Process torProcess)
- throws InterruptedException, PluginException {
+ throws InterruptedException, IOException {
// 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.
@@ -91,7 +85,7 @@ class WindowsTorPlugin extends JavaTorPlugin {
}
});
// Wait for the startup result
- if (!success.take()) throw new PluginException();
+ if (!success.take()) throw new IOException();
}
@Override
diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
index 52ffb400f..f5bb53141 100644
--- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
+++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Multiset;
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.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -15,7 +16,8 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
-import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType;
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
@@ -45,11 +47,11 @@ import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
-import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
+import static org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
@@ -113,6 +115,9 @@ public class BridgeTest extends BrambleTestCase {
@IoExecutor
Executor ioExecutor;
@Inject
+ @EventExecutor
+ Executor eventExecutor;
+ @Inject
@WakefulIoExecutor
Executor wakefulIoExecutor;
@Inject
@@ -185,9 +190,9 @@ public class BridgeTest extends BrambleTestCase {
return singletonList(params.bridge);
}
};
- factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
- networkManager, locationUtils, eventBus, torSocketFactory,
- backoffFactory, resourceProvider, bridgeProvider,
+ factory = new UnixTorPluginFactory(ioExecutor, eventExecutor,
+ wakefulIoExecutor, networkManager, locationUtils, eventBus,
+ torSocketFactory, backoffFactory, bridgeProvider,
batteryManager, clock, crypto, torDir, torSocksPort,
torControlPort);
}
@@ -207,7 +212,7 @@ public class BridgeTest extends BrambleTestCase {
DuplexPlugin duplexPlugin =
factory.createPlugin(new TestPluginCallback());
assertNotNull(duplexPlugin);
- UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
+ TorPlugin plugin = (TorPlugin) duplexPlugin;
LOG.warning("Testing " + params.bridge);
try {
diff --git a/bramble-java/src/test/java/org/briarproject/bramble/test/BrambleJavaIntegrationTestComponent.java b/bramble-java/src/test/java/org/briarproject/bramble/test/BrambleJavaIntegrationTestComponent.java
index 3f47a3541..8443e578c 100644
--- a/bramble-java/src/test/java/org/briarproject/bramble/test/BrambleJavaIntegrationTestComponent.java
+++ b/bramble-java/src/test/java/org/briarproject/bramble/test/BrambleJavaIntegrationTestComponent.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.BrambleJavaModule;
import org.briarproject.bramble.mailbox.ModularMailboxModule;
import org.briarproject.bramble.plugin.tor.BridgeTest;
-import org.briarproject.bramble.plugin.tor.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import javax.inject.Singleton;
diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index d05cbd064..dec9db1c1 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -116,7 +116,6 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
- implementation 'org.briarproject:dont-kill-me-lib:0.2.5'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "org.jsoup:jsoup:$jsoup_version"
implementation 'info.guardianproject.panic:panic:1.0'
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
index 468782cd4..e58baf4ad 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
@@ -1,5 +1,6 @@
package org.briarproject.briar.android;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleAppComponent;
@@ -25,12 +26,11 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.AndroidExecutor;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.mailbox.ModularMailboxModule;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
-import org.briarproject.bramble.plugin.tor.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
index 3fab62163..87f866021 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
@@ -15,12 +15,12 @@ import android.os.IBinder;
import com.bumptech.glide.Glide;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logout.HideUiActivity;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
index 8c72093ba..9873748a4 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
@@ -5,7 +5,7 @@ import android.transition.Transition;
import android.view.Window;
import android.widget.CheckBox;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
index 47dfa1ba8..34f4edcee 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java
@@ -4,13 +4,13 @@ import android.app.Activity;
import android.content.Intent;
import android.os.IBinder;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.BriarService;
import org.briarproject.briar.android.BriarService.BriarServiceConnection;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
index 85cc559e4..9ccafba31 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java
@@ -24,7 +24,7 @@ import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.plugin.tor.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java
index 73c7d24ae..d52ab51b0 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.settings;
import android.content.Context;
import org.briarproject.bramble.api.system.LocationUtils;
-import org.briarproject.bramble.plugin.tor.CircumventionProvider;
+import org.briarproject.bramble.plugin.tor.wrapper.CircumventionProvider;
import org.briarproject.briar.R;
import org.briarproject.nullsafety.NotNullByDefault;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/ExpiredOldAndroidActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/ExpiredOldAndroidActivity.java
index c9fe83229..a5e7d271c 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/splash/ExpiredOldAndroidActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/ExpiredOldAndroidActivity.java
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.splash;
import android.content.Intent;
import android.os.Bundle;
-import org.briarproject.bramble.api.system.AndroidWakeLockManager;
+import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle
index 198bc6e14..5795fc5a2 100644
--- a/briar-android/witness.gradle
+++ b/briar-android/witness.gradle
@@ -120,7 +120,6 @@ 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.bouncycastle:bcprov-jdk15on:1.65:bcprov-jdk15on-1.65.jar:e78f96eb59066c94c94fb2d6b5eb80f52feac6f5f9776898634f8addec6e2137',
- 'org.briarproject:dont-kill-me-lib:0.2.5:dont-kill-me-lib-0.2.5.aar:55cd9d511b7016ab573905d64bc54e222e2633144d36389192b8b34485b31b9d',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
@@ -136,12 +135,11 @@ dependencyVerification {
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c',
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
- 'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
- 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.20:kotlin-stdlib-jdk7-1.6.20.jar:aa2fa2e81355c4d98dd97da2169bf401f842261378f5b1cbea1aa11855d67620',
+ 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
- 'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
+ 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1:kotlinx-coroutines-android-1.4.1.jar:d4cadb673b2101f1ee5fbc147956ac78b1cfd9cc255fb53d3aeb88dff11d99ca',
'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.1:kotlinx-coroutines-core-jvm-1.4.1.jar:6d2f87764b6638f27aff12ed380db4b63c9d46ba55dc32683a650598fa5a3e22',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',