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',