Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
50538b6908 Add AMP cache broker for Snowflake. 2022-10-15 17:56:46 +01:00
547 changed files with 9593 additions and 16724 deletions

View File

@@ -83,19 +83,30 @@ android test:
test_reproducible:
stage: check_reproducibility
script:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[APP]='briar' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
- "curl -X POST -F token=${RELEASE_JAR_CHECK_TOKEN} -F ref=main -F variables[APP]='briar-headless' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/307/trigger/pipeline"
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only:
- tags
mailbox integration test:
.optional_tests:
stage: optional_tests
extends: .base-test
bridge test:
extends: .optional_tests
rules:
- changes:
- mailbox-integration-tests/**/*
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: false
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 4h
mailbox integration test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
- if: '$CI_COMMIT_TAG == null'

View File

@@ -35,6 +35,9 @@
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="Groovy">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />

28
.idea/runConfigurations/BridgeTest.xml generated Normal file
View File

@@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--tests &quot;org.briarproject.bramble.plugin.tor.BridgeTest&quot;" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-java:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +1,25 @@
import com.android.build.gradle.tasks.MergeResources
apply plugin: 'com.android.library'
apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion 33
buildToolsVersion '33.0.0'
compileSdkVersion 30
buildToolsVersion '30.0.3'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
versionCode 10501
versionName "1.5.1"
minSdkVersion 16
targetSdkVersion 30
versionCode 10412
versionName "1.4.12"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments disableAnalytics: 'true'
}
compileOptions {
@@ -40,19 +41,8 @@ configurations {
}
dependencies {
api 'org.briarproject:dont-kill-me-lib:0.2.7'
// 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
// below and the compiler can find them
implementation project(':bramble-api')
implementation project(':bramble-core')
implementation project(path: ':bramble-core', configuration: 'default')
implementation 'androidx.annotation:annotation:1.5.0'
implementation "org.briarproject:onionwrapper-android:$onionwrapper_version"
tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
tor "org.briarproject:snowflake-android:$snowflake_version"
@@ -62,33 +52,61 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
}
def torBinariesDir = 'src/main/res/raw'
def torLibsDir = 'src/main/jniLibs'
task cleanTorBinaries {
outputs.dir torLibsDir
doLast {
delete fileTree(torLibsDir)
delete fileTree(torBinariesDir) { include '*.zip' }
delete fileTree(torLibsDir) { include '**/*.so' }
}
}
clean.dependsOn cleanTorBinaries
task unpackTorBinaries {
outputs.dir torLibsDir
doLast {
copy {
from configurations.tor.collect { zipTree(it) }
into torLibsDir
configurations.tor.each { outer ->
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'armeabi-v7a/lib$1.so'
}
} else if (inner.name.endsWith('_arm64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'arm64-v8a/lib$1.so'
}
} else if (inner.name.endsWith('_x86_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86/lib$1.so'
}
} else if (inner.name.endsWith('_x86_64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86_64/lib$1.so'
}
}
}
}
}
dependsOn cleanTorBinaries
}
preBuild.dependsOn unpackTorBinaries
tasks.withType(MergeResources) {
inputs.dir torBinariesDir
inputs.dir torLibsDir
dependsOn unpackTorBinaries
}

View File

@@ -1,28 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.bramble">
<manifest
package="org.briarproject.bramble"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 and Nubia devices running API 32 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="31" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:allowBackup="false"

View File

@@ -8,9 +8,7 @@ import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.AndroidSystemModule;
import org.briarproject.bramble.system.AndroidTaskSchedulerModule;
import org.briarproject.bramble.system.AndroidWakeLockModule;
import org.briarproject.bramble.system.AndroidWakefulIoExecutorModule;
import org.briarproject.bramble.system.DefaultThreadFactoryModule;
import dagger.Module;
@@ -20,8 +18,6 @@ import dagger.Module;
AndroidSystemModule.class,
AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class,
AndroidWakeLockModule.class,
DefaultThreadFactoryModule.class,
CircumventionModule.class,
DnsModule.class,
ReportingModule.class,

View File

@@ -20,6 +20,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
@@ -104,11 +105,15 @@ class AndroidAccountManager extends AccountManagerImpl
}
files.add(appContext.getFilesDir());
addIfNotNull(files, appContext.getExternalCacheDir());
for (File file : appContext.getExternalCacheDirs()) {
addIfNotNull(files, file);
if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) {
addIfNotNull(files, file);
}
}
for (File file : appContext.getExternalMediaDirs()) {
addIfNotNull(files, file);
if (SDK_INT >= 21) {
for (File file : appContext.getExternalMediaDirs()) {
addIfNotNull(files, file);
}
}
// Clear the cache directory but don't delete it
File cacheDir = appContext.getCacheDir();

View File

@@ -0,0 +1,19 @@
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();
}

View File

@@ -0,0 +1,38 @@
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.
* <p>
* This method should only be used for lifecycle management tasks that
* can't be run on an executor.
*/
void executeWakefully(Runnable r, String tag);
}

View File

@@ -118,11 +118,6 @@ class AndroidNetworkManager implements NetworkManager, Service {
try {
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
// Research into Android's behavior to check network connectivity
// (https://code.briarproject.org/briar/public-mesh-research/-/issues/19)
// has shown that NetworkInfo#isConnected() returns true if the device
// is connected to any Wifi, independent of whether any specific IP
// address can be reached using it or any domain names can be resolved.
boolean wifi = false, ipv6Only = false;
if (connected) {
wifi = net.getType() == TYPE_WIFI;

View File

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

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.annotation.SuppressLint;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -50,17 +49,16 @@ import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@SuppressLint("MissingPermission")
class AndroidBluetoothPlugin extends
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
@@ -99,11 +97,6 @@ class AndroidBluetoothPlugin extends
this.clock = clock;
}
@Override
protected boolean isBluetoothAccessible() {
return hasBtConnectPermission(app);
}
@Override
public void start() throws PluginException {
super.start();
@@ -254,7 +247,7 @@ class AndroidBluetoothPlugin extends
} else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
// Ignore Bluetooth LE devices
if (d.getType() != DEVICE_TYPE_LE) {
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " +

View File

@@ -3,7 +3,6 @@ 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;
@@ -14,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.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;

View File

@@ -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;
@@ -33,10 +33,8 @@ class AndroidBluetoothTransportConnection
super(plugin);
this.connectionLimiter = connectionLimiter;
this.socket = socket;
InputStream socketIn = socket.getInputStream();
if (socketIn == null) throw new IOException();
in = timeoutMonitor.createTimeoutInputStream(socketIn,
plugin.getMaxIdleTime() * 2L);
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
wakeLock = wakeLockManager.createWakeLock("BluetoothConnection");
wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress();
@@ -50,9 +48,7 @@ class AndroidBluetoothTransportConnection
@Override
protected OutputStream getOutputStream() throws IOException {
OutputStream socketOut = socket.getOutputStream();
if (socketOut == null) throw new IOException();
return socketOut;
return socket.getOutputStream();
}
@Override

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp;
import android.annotation.TargetApi;
import android.app.Application;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
@@ -36,6 +37,7 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.Collections.singletonList;
@@ -116,7 +118,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null) {
return getWifiClientIpv6Address();
return SDK_INT >= 21 ? getWifiClientIpv6Address() : null;
}
// Use the wifi IPv4 address to determine which interface's IPv6
// address we should return (if the interface has a suitable address)
@@ -170,6 +172,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
* Returns a link-local IPv6 address for the wifi client interface, or null
* if there's no such interface or it doesn't have a suitable address.
*/
@TargetApi(21)
@Nullable
private InetAddress getWifiClientIpv6Address() {
// https://issuetracker.google.com/issues/175055271
@@ -231,6 +234,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// On API 21 and later, a socket that is not created with the wifi
// network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
// https://issuetracker.google.com/issues/175055271
try {
for (Network net : connectivityManager.getAllNetworks()) {
@@ -298,7 +302,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null) {
if (wifi == null && SDK_INT >= 21) {
InetAddress ipv6 = getWifiClientIpv6Address();
if (ipv6 != null) return new Pair<>(ipv6, false);
}

View File

@@ -0,0 +1,238 @@
package org.briarproject.bramble.plugin.tor;
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
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 java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
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 {
private static final List<String> LIBRARY_ARCHITECTURES =
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
private static final String TOR_LIB_NAME = "libtor.so";
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
private static final Logger LOG =
getLogger(AndroidTorPlugin.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,
AndroidWakeLockManager wakeLockManager,
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);
this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
torLib = new File(nativeLibDir, TOR_LIB_NAME);
obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
snowflakeLib = new File(nativeLibDir, SNOWFLAKE_LIB_NAME);
}
@Override
protected int getProcessId() {
return android.os.Process.myPid();
}
@Override
protected long getLastUpdateTime() {
try {
PackageManager pm = app.getPackageManager();
PackageInfo pi = pm.getPackageInfo(app.getPackageName(), 0);
return pi.lastUpdateTime;
} catch (NameNotFoundException e) {
throw new AssertionError(e);
}
}
@Override
protected void enableNetwork(boolean enable) throws IOException {
if (enable) wakeLock.acquire();
super.enableNetwork(enable);
if (!enable) wakeLock.release();
}
@Override
@ChecksSdkIntAtLeast(api = 25)
protected boolean canVerifyLetsEncryptCerts() {
return SDK_INT >= 25;
}
@Override
public void stop() {
super.stop();
wakeLock.release();
}
@Override
protected File getTorExecutableFile() {
return torLib.exists() ? torLib : super.getTorExecutableFile();
}
@Override
protected File getObfs4ExecutableFile() {
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
}
@Override
protected File getSnowflakeExecutableFile() {
return snowflakeLib.exists()
? snowflakeLib : super.getSnowflakeExecutableFile();
}
@Override
protected void installTorExecutable() throws IOException {
installExecutable(super.getTorExecutableFile(), torLib, TOR_LIB_NAME);
}
@Override
protected void installObfs4Executable() throws IOException {
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib,
OBFS4_LIB_NAME);
}
@Override
protected void installSnowflakeExecutable() throws IOException {
installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib,
SNOWFLAKE_LIB_NAME);
}
private void installExecutable(File extracted, File lib, String libName)
throws IOException {
if (lib.exists()) {
// If an older version left behind a binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted old binary");
else LOG.info("Failed to delete old binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(libName, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(lib.getAbsolutePath());
}
}
private void extractLibraryFromApk(String libName, File dest)
throws IOException {
File sourceDir = new File(app.getApplicationInfo().sourceDir);
if (sourceDir.isFile()) {
// Look for other APK files in the same directory, if we're allowed
File parent = sourceDir.getParentFile();
if (parent != null) sourceDir = parent;
}
List<String> libPaths = getSupportedLibraryPaths(libName);
for (File apk : findApkFiles(sourceDir)) {
ZipInputStream zin = new ZipInputStream(new FileInputStream(apk));
for (ZipEntry e = zin.getNextEntry(); e != null;
e = zin.getNextEntry()) {
if (libPaths.contains(e.getName())) {
if (LOG.isLoggable(INFO)) {
LOG.info("Extracting " + e.getName()
+ " from " + apk.getAbsolutePath());
}
extract(zin, dest); // Zip input stream will be closed
return;
}
}
zin.close();
}
throw new FileNotFoundException(libName);
}
/**
* Returns all files with the extension .apk or .APK under the given root.
*/
private List<File> findApkFiles(File root) {
List<File> files = new ArrayList<>();
findApkFiles(root, files);
return files;
}
private void findApkFiles(File f, List<File> files) {
if (f.isFile() && f.getName().toLowerCase().endsWith(".apk")) {
files.add(f);
} else if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) {
for (File child : children) findApkFiles(child, files);
}
}
}
/**
* Returns the paths at which libraries with the given name would be found
* inside an APK file, for all architectures supported by the device, in
* order of preference.
*/
private List<String> getSupportedLibraryPaths(String libName) {
List<String> architectures = new ArrayList<>();
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (LIBRARY_ARCHITECTURES.contains(abi)) {
architectures.add("lib/" + abi + "/" + libName);
}
}
return architectures;
}
}

View File

@@ -2,11 +2,9 @@ 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;
@@ -15,13 +13,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.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.AndroidTorWrapper;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.LocationUtils;
import org.briarproject.onionwrapper.TorWrapper;
import java.io.File;
import java.util.concurrent.Executor;
@@ -31,7 +28,6 @@ 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
@@ -43,13 +39,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,
@@ -59,8 +55,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@TorControlPort int torControlPort,
Application app,
AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app;
@@ -83,18 +79,12 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
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);
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);
}
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.bramble.system;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Locale;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.content.Context.TELEPHONY_SERVICE;
@NotNullByDefault
class AndroidLocationUtils implements LocationUtils {
private static final Logger LOG =
Logger.getLogger(AndroidLocationUtils.class.getName());
private final Context appContext;
@Inject
AndroidLocationUtils(Application app) {
appContext = app.getApplicationContext();
}
/**
* This guesses the current country from the first of these sources that
* succeeds (also in order of likelihood of being correct):
*
* <ul>
* <li>Phone network. This works even when no SIM card is inserted, or a
* foreign SIM card is inserted.</li>
* <li>SIM card. This is only an heuristic and assumes the user is not
* roaming.</li>
* <li>User locale. This is an even worse heuristic.</li>
* </ul>
*
* Note: this is very similar to <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java">
* this API</a> except it seems that Google doesn't want us to use it for
* some reason - both that class and {@code Context.COUNTRY_CODE} are
* annotated {@code @hide}.
*/
@Override
@SuppressLint("DefaultLocale")
public String getCurrentCountry() {
String countryCode = getCountryFromPhoneNetwork();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
LOG.info("Falling back to SIM card country");
countryCode = getCountryFromSimCard();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
LOG.info("Falling back to user-defined locale");
return Locale.getDefault().getCountry();
}
private String getCountryFromPhoneNetwork() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o;
return tm == null ? "" : tm.getNetworkCountryIso();
}
private String getCountryFromSimCard() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o;
return tm == null ? "" : tm.getSimCountryIso();
}
}

View File

@@ -6,6 +6,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.StrictMode;
import android.provider.Settings;
@@ -14,23 +15,19 @@ import org.briarproject.nullsafety.NotNullByDefault;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.os.Build.FINGERPRINT;
import static android.os.Build.SERIAL;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myPid;
import static android.os.Process.myTid;
import static android.os.Process.myUid;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@NotNullByDefault
class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
private static final int SEED_LENGTH = 32;
private final Context appContext;
@Inject
@@ -42,27 +39,22 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
@Override
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
super.writeToEntropyPool(out);
out.writeInt(myPid());
out.writeInt(myTid());
out.writeInt(myUid());
if (FINGERPRINT != null) out.writeUTF(FINGERPRINT);
if (SERIAL != null) out.writeUTF(SERIAL);
out.writeInt(android.os.Process.myPid());
out.writeInt(android.os.Process.myTid());
out.writeInt(android.os.Process.myUid());
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id);
// On API 31 and higher we need permission to access bonded devices
if (SDK_INT < 31) {
Parcel parcel = Parcel.obtain();
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
@SuppressLint("MissingPermission")
Set<BluetoothDevice> deviceSet = bt.getBondedDevices();
for (BluetoothDevice device : deviceSet)
parcel.writeParcelable(device, 0);
}
out.write(parcel.marshall());
parcel.recycle();
Parcel parcel = Parcel.obtain();
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices())
parcel.writeParcelable(device, 0);
}
out.write(parcel.marshall());
parcel.recycle();
}
@Override
@@ -70,6 +62,27 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
// Silence strict mode
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
super.writeSeed();
if (SDK_INT <= 18) applyOpenSslFix();
StrictMode.setThreadPolicy(tp);
}
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() {
byte[] seed = new UnixSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH);
try {
// Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, (Object) seed);
// Mix the output of the Linux PRNG into the OpenSSL PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) throw new IOException();
} catch (Exception e) {
throw new SecurityException(e);
}
}
}

View File

@@ -1,14 +1,12 @@
package org.briarproject.bramble.system;
import android.app.Application;
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;
import org.briarproject.onionwrapper.AndroidLocationUtilsFactory;
import org.briarproject.onionwrapper.LocationUtils;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler;
@@ -48,9 +46,8 @@ public class AndroidSystemModule {
}
@Provides
@Singleton
LocationUtils provideLocationUtils(Application app) {
return AndroidLocationUtilsFactory.createAndroidLocationUtils(app);
LocationUtils provideLocationUtils(AndroidLocationUtils locationUtils) {
return locationUtils;
}
@Provides
@@ -72,4 +69,11 @@ public class AndroidSystemModule {
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
return provider;
}
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(
AndroidWakeLockManagerImpl wakeLockManager) {
return wakeLockManager;
}
}

View File

@@ -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;
@@ -41,7 +41,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.system.AlarmConstants.EXTRA_PID;
import static org.briarproject.bramble.system.AlarmConstants.REQUEST_ALARM;
import static org.briarproject.bramble.util.AndroidUtils.getImmutableFlags;
@ThreadSafe
@NotNullByDefault
@@ -200,7 +199,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
Intent i = new Intent(app, AlarmReceiver.class);
i.putExtra(EXTRA_PID, android.os.Process.myPid());
return PendingIntent.getBroadcast(app, REQUEST_ALARM, i,
getImmutableFlags(FLAG_CANCEL_CURRENT));
FLAG_CANCEL_CURRENT);
}
private class ScheduledTask

View File

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

View File

@@ -0,0 +1,74 @@
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");
}
}
}
}
}

View File

@@ -0,0 +1,125 @@
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;
}
}
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManagerFactory;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AndroidWakeLockModule {
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(Application app,
ScheduledExecutorService scheduledExecutorService) {
return AndroidWakeLockManagerFactory.createAndroidWakeLockManager(app,
scheduledExecutorService);
}
}

View File

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

View File

@@ -0,0 +1,130 @@
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");
}
}
}
}

View File

@@ -0,0 +1,22 @@
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();
}

View File

@@ -11,20 +11,20 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import javax.annotation.Nullable;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
import static java.lang.Runtime.getRuntime;
import static java.util.Arrays.asList;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@@ -38,12 +38,14 @@ public class AndroidUtils {
private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() {
return asList(Build.SUPPORTED_ABIS);
}
public static boolean hasBtConnectPermission(Context ctx) {
return SDK_INT < 31 || ctx.checkPermission(BLUETOOTH_CONNECT, myPid(),
myUid()) == PERMISSION_GRANTED;
List<String> 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;
}
public static String getBluetoothAddress(Context ctx,
@@ -53,9 +55,6 @@ public class AndroidUtils {
public static Pair<String, String> getBluetoothAddressAndMethod(Context ctx,
BluetoothAdapter adapter) {
// If we don't have permission to access the adapter's address, let
// the caller know we can't find it
if (!hasBtConnectPermission(ctx)) return new Pair<>("", "");
// Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds")
String address = adapter.getAddress();
@@ -124,14 +123,20 @@ public class AndroidUtils {
return new String[] {"image/jpeg", "image/png", "image/gif"};
}
@Nullable
public static String getSystemProperty(String propName) {
try {
Process p = getRuntime().exec("getprop " + propName);
Scanner s = new Scanner(p.getInputStream());
String line = s.nextLine();
s.close();
return line;
} catch (SecurityException | IOException e) {
return null;
}
}
public static boolean isUiThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
public static int getImmutableFlags(int flags) {
if (SDK_INT >= 23) {
return FLAG_IMMUTABLE | flags;
}
return flags;
}
}

View File

@@ -87,10 +87,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
File potatoFile = new File(potatoDir, "file");
File filesDir = new File(testDir, "filesDir");
File externalCacheDir = new File(testDir, "externalCacheDir");
File externalCacheDir1 = new File(testDir, "externalCacheDir1");
File externalCacheDir2 = new File(testDir, "externalCacheDir2");
File externalMediaDir1 = new File(testDir, "externalMediaDir1");
File externalMediaDir2 = new File(testDir, "externalMediaDir2");
context.checking(new Expectations() {{
oneOf(prefs).edit();
@@ -113,12 +109,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
will(returnValue(cacheDir));
oneOf(app).getExternalCacheDir();
will(returnValue(externalCacheDir));
oneOf(app).getExternalCacheDirs();
will(returnValue(
new File[] {externalCacheDir1, externalCacheDir2}));
oneOf(app).getExternalMediaDirs();
will(returnValue(
new File[] {externalMediaDir1, externalMediaDir2}));
}});
assertTrue(dbDir.mkdirs());
@@ -135,10 +125,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(potatoFile.createNewFile());
assertTrue(filesDir.mkdirs());
assertTrue(externalCacheDir.mkdirs());
assertTrue(externalCacheDir1.mkdirs());
assertTrue(externalCacheDir2.mkdirs());
assertTrue(externalMediaDir1.mkdirs());
assertTrue(externalMediaDir2.mkdirs());
accountManager.deleteAccount();
@@ -156,10 +142,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(potatoFile.exists());
assertFalse(filesDir.exists());
assertFalse(externalCacheDir.exists());
assertFalse(externalCacheDir1.exists());
assertFalse(externalCacheDir2.exists());
assertFalse(externalMediaDir1.exists());
assertFalse(externalMediaDir2.exists());
}
@After

View File

@@ -2,66 +2,164 @@ dependencyVerification {
verify = [
'androidx.annotation:annotation:1.5.0:annotation-1.5.0.jar:261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
'com.android.tools.analytics-library:tracker:30.0.3:tracker-30.0.3.jar:5d0ef35bf6733e96210b5085a2a202152921bf834d345959dce1ca3369b528df',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:7.0.3:apksig-7.0.3.jar:012337a2803c9a30dfc41dcbc6450686ee9e5f582549f7f126479f743a343ec9',
'com.android.tools.build:apkzlib:7.0.3:apkzlib-7.0.3.jar:b31e53174c92db83c5cc6e7dac6734ea4e907a72e452c2bf1818dfd082c59397',
'com.android.tools.build:builder-model:7.0.3:builder-model-7.0.3.jar:483f99d7494a5bed027e1e8d29111384cf535d4842f0be5a79805bd44bb68d4e',
'com.android.tools.build:builder-test-api:7.0.3:builder-test-api-7.0.3.jar:f6de4bc2cef545e8367bf82d7c733829c7be3b0b3b8b09fd8c58f2150e59ab46',
'com.android.tools.build:builder:7.0.3:builder-7.0.3.jar:c6952da0094b094c2ba0fe84c675622097c5d9b9f9beb53485b860320540cf1d',
'com.android.tools.build:manifest-merger:30.0.3:manifest-merger-30.0.3.jar:72b346ba6318b4b6260e6e49df4bea5da2e12329ab6c2beb2269c49a9f51f178',
'com.android.tools.ddms:ddmlib:30.0.3:ddmlib-30.0.3.jar:7a914a68ab93393657297234e2f37b22410ae9a433cba692ce8c727c9607e3bb',
'com.android.tools.external.com-intellij:intellij-core:30.0.3:intellij-core-30.0.3.jar:1ebe858d3f58eeaa8c06507f8ac0f1c7051e6c61f35a70f3c3967d5734d3abc5',
'com.android.tools.external.com-intellij:kotlin-compiler:30.0.3:kotlin-compiler-30.0.3.jar:ed00e441f427cb4e0d418287b9da30b12b7f735f9af32e6b5d3dc960b6a742fc',
'com.android.tools.external.org-jetbrains:uast:30.0.3:uast-30.0.3.jar:a77801bee6ff509910e459525c9c34d7f04b066ade123547f16f1917548eadea',
'com.android.tools.layoutlib:layoutlib-api:30.0.3:layoutlib-api-30.0.3.jar:4caa87e9ca2e11315f650d576cd59fec1793373bc3fca3f6d53c029e7534e7c4',
'com.android.tools.lint:lint-api:30.0.3:lint-api-30.0.3.jar:bcecbd2f752a6560096a9029a47d1de6bd788a51bab505c5ebfba6a18524b983',
'com.android.tools.lint:lint-checks:30.0.3:lint-checks-30.0.3.jar:25a7cd42dc3ad502337f131fb8b7e873c53301db0a67b1c64dd4ae7a8eb66cec',
'com.android.tools.lint:lint-gradle:30.0.3:lint-gradle-30.0.3.jar:94544d6147a809bf2fd3440e51f28a4e42e547d74aab53eefd74938cdad42c26',
'com.android.tools.lint:lint-model:30.0.3:lint-model-30.0.3.jar:0b940a7f575c2ff5cbd038260f41dde686a93c672213881ead3ce8af3513b396',
'com.android.tools.lint:lint:30.0.3:lint-30.0.3.jar:ee4f11001e0c7e3b776e0d67399ad354b19b0f168822ec2b7db47c0910ed227d',
'com.android.tools:annotations:30.0.3:annotations-30.0.3.jar:5c1944982fda8555855c4f5422fabf0dc8e2306e1f5460e9ad82dae71316bc31',
'com.android.tools:common:30.0.3:common-30.0.3.jar:8751efaaf2c2ddd1f0a37526c794347def6a3057ca9fc510307c13a6cf0d036f',
'com.android.tools:dvlib:30.0.3:dvlib-30.0.3.jar:5affafcec390041e5afd64cb924153f5e474db47ee8ccc2f555b495083141233',
'com.android.tools:repository:30.0.3:repository-30.0.3.jar:0a40c6f16c506903ce2c609affd8228aceda73a69d93dfa42d4f02b8491449f6',
'com.android.tools:sdk-common:30.0.3:sdk-common-30.0.3.jar:b45570a380360236ffee0f6bb593d66b673bad3834dfe0d6c9871fa7188ee0eb',
'com.android.tools:sdklib:30.0.3:sdklib-30.0.3.jar:7088f20a414fab170a21e457825e14ebe099f753558e02c8acc12c67eb412162',
'com.android:signflinger:7.0.3:signflinger-7.0.3.jar:903a4536db3e96b4e1e1dc1e400eb0b91bf7866d9b39cd7ec94d75dde158f152',
'com.android:zipflinger:7.0.3:zipflinger-7.0.3.jar:fd209c960a3eff7a339e6fcba07d5e9ef4604d1633c69ab2df987460d9804140',
'com.beust:jcommander:1.78:jcommander-1.78.jar:7891debb84b5f83e9bd57593ebece3399abbe0fd938cf306b3534c57913b9615',
'com.github.javaparser:javaparser-core:3.17.0:javaparser-core-3.17.0.jar:23f5c982e1c7771423d37d52c774e8d2e80fd7ea7305ebe448797a96f67e6fca',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.45:dagger-compiler-2.45.jar:5617dfb994537dba5b41f3744a6dd13ec3cd99789c065e0d5c6fa9f21cf7ca25',
'com.google.dagger:dagger-producers:2.45:dagger-producers-2.45.jar:a05abb4c3ccf6bb0f056bdcb5ef973898ecf172952ab5948a824aeea6c86ecaa',
'com.google.dagger:dagger-spi:2.45:dagger-spi-2.45.jar:7cd6f0b09d88e64a9c97bc80e544ab8ac8fdee9301754413585a74cf64222b27',
'com.google.dagger:dagger:2.45:dagger-2.45.jar:f011cae7d2c0fb7ea17c34e05bc10e768b1081a5892ad019cf1fdb0e125c49c1',
'com.google.devtools.ksp:symbol-processing-api:1.7.0-1.0.6:symbol-processing-api-1.7.0-1.0.6.jar:adc29417be5ca9ff42118105fea4e36d9ef44987abfc41432309371a60198941',
'com.google.errorprone:error_prone_annotations:2.7.1:error_prone_annotations-2.7.1.jar:cd5257c08a246cf8628817ae71cb822be192ef91f6881ca4a3fcff4f1de1cff3',
'com.google.code.gson:gson:2.8.6:gson-2.8.6.jar:c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f',
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.4:error_prone_annotations-2.3.4.jar:baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:31.0.1-jre:guava-31.0.1-jre.jar:d5be94d65e87bd219fb3193ad1517baa55a3b88fc91d21cf735826ab5af087b9',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'com.google.guava:guava:30.1-jre:guava-30.1-jre.jar:e6dd072f9d3fe02a4600688380bd422bdac184caf6fe2418cfdd0934f09432aa',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.8:istack-commons-runtime-3.0.8.jar:4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef',
'com.sun.xml.fastinfoset:FastInfoset:1.2.16:FastInfoset-1.2.16.jar:056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511',
'com.thoughtworks.qdox:qdox:1.12.1:qdox-1.12.1.jar:21fba22f830e9268f07cf4ab2d99e8181abbdcb0cb91ee0228eb3cb918dcdd1d',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-io:commons-io:2.4:commons-io-2.4.jar:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'info.picocli:picocli:4.5.2:picocli-4.5.2.jar:b4395e9a67932616efd2245d984bf5fcd453c2c5049558c3ce959ac2af4d3fac',
'it.unimi.dsi:fastutil:8.4.0:fastutil-8.4.0.jar:2ad2824a4a0a0eb836b52ee2fc84ba2134f44bce7bfa54015ae3f31c710a3071',
'jakarta.activation:jakarta.activation-api:1.2.1:jakarta.activation-api-1.2.1.jar:8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b',
'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2:jakarta.xml.bind-api-2.3.2.jar:69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'jline:jline:2.14.6:jline-2.14.6.jar:97d1acaac82409be42e622d7a54d3ae9d08517e8aefdea3d2ba9791150c2f02d',
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:5.6.0:jna-platform-5.6.0.jar:9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7',
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:dont-kill-me-lib:0.2.7:dont-kill-me-lib-0.2.7.aar:8a9540941fd927e1c127096a7a9b4aa61ce2f2965d2e24f849be92f9e57213c4',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'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:onionwrapper-android:0.0.3:onionwrapper-android-0.0.3.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
'org.briarproject:onionwrapper-core:0.0.3:onionwrapper-core-0.0.3.jar:2645dd7e4f2e73fc2ba30696aeac4c960ef8b8f405d85e529fdf8b105ef374f1',
'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',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.apache.ant:ant-antlr:1.10.9:ant-antlr-1.10.9.jar:7623dc9d0f20ea713290c6bf1a23f4c059447aef7ff9f5b2be75960f3f028d2e',
'org.apache.ant:ant-junit:1.10.9:ant-junit-1.10.9.jar:960bdc8827954d62206ba42d0a68a7ee4476175ba47bb113e17e77cce7394630',
'org.apache.ant:ant-launcher:1.10.9:ant-launcher-1.10.9.jar:fcce891f57f3be72149ff96ac2a80574165b3e0839866b95d24528f3027d50c1',
'org.apache.ant:ant:1.10.9:ant-1.10.9.jar:0715478af585ea80a18985613ebecdc7922122d45b2c3c970ff9b352cddb75fc',
'org.apache.commons:commons-compress:1.20:commons-compress-1.20.jar:0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.14:obfs4proxy-android-0.0.14.jar:ad9b1ee4757b05867a19e993147bbb018bddd1f26ce3da746d5f037d5991a8c8',
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
'org.codehaus.groovy:groovy-ant:3.0.7:groovy-ant-3.0.7.jar:6ed2ba82813d128f7050c24142e87b3dc2ad8b504786280eb03e81f0cf6a5793',
'org.codehaus.groovy:groovy-astbuilder:3.0.7:groovy-astbuilder-3.0.7.jar:b290451eb1583666e906c41f7d14747b4cc96363c99c478b244634fd5dfc9013',
'org.codehaus.groovy:groovy-cli-picocli:3.0.7:groovy-cli-picocli-3.0.7.jar:71b4bd11fb30a9c7b5618e22122c9c5141958fb27f4dcf0068b6f715088f6916',
'org.codehaus.groovy:groovy-console:3.0.7:groovy-console-3.0.7.jar:0541b358b6b8e5363215026736168fccfec1d91bac678d066fa77349eeeaa5dd',
'org.codehaus.groovy:groovy-datetime:3.0.7:groovy-datetime-3.0.7.jar:b9823d14b1a4f94236ae2f8a471701aab17e093e1b33402b91550b5c8dd88f04',
'org.codehaus.groovy:groovy-docgenerator:3.0.7:groovy-docgenerator-3.0.7.jar:bf53f7a11c9eb1e278e1b8ed2714c741bcf781235c803ad3ba1555f2614573f3',
'org.codehaus.groovy:groovy-groovydoc:3.0.7:groovy-groovydoc-3.0.7.jar:86b24dfc23c005066ab83927cdb54177f06c9531773f2e2d2ecc9a131f7c2677',
'org.codehaus.groovy:groovy-groovysh:3.0.7:groovy-groovysh-3.0.7.jar:5c40e78cbc09726aedd1c75fab112d245d665d6294870f9119e6cd3013ed14ab',
'org.codehaus.groovy:groovy-jmx:3.0.7:groovy-jmx-3.0.7.jar:0a89f3007884eb156751937d93382038b83d39c7c2f0ab156ebf251a7251f2ab',
'org.codehaus.groovy:groovy-json:3.0.7:groovy-json-3.0.7.jar:df1f0ee475e3fc93a6a0d17548294e160cca5de6d9d36817a7be1fbe650de03b',
'org.codehaus.groovy:groovy-jsr223:3.0.7:groovy-jsr223-3.0.7.jar:1dbd969595332416193baa660fbb45743d19696eaa25fe98e591a2739e13517e',
'org.codehaus.groovy:groovy-macro:3.0.7:groovy-macro-3.0.7.jar:c6cc06df526b39e2c359e2435f0071594c5a1c7babafaa6c184fdd8fa931531f',
'org.codehaus.groovy:groovy-nio:3.0.7:groovy-nio-3.0.7.jar:db54c577882b294cd8c975ec5451596441baf54781319c61627dca0e0c2361ef',
'org.codehaus.groovy:groovy-servlet:3.0.7:groovy-servlet-3.0.7.jar:5b6a909bf501c209adfb6205b9e740649609074455fd979bf9da4853e6ff9a39',
'org.codehaus.groovy:groovy-sql:3.0.7:groovy-sql-3.0.7.jar:252bb6c74e1a9f41756ad4fbd3b0d2eddc93bb61109961dd1952a37bf2d57a64',
'org.codehaus.groovy:groovy-swing:3.0.7:groovy-swing-3.0.7.jar:bd942032d9328d54c6679c49a41f6caa0d4a0039ebe598493b8a647730d98cff',
'org.codehaus.groovy:groovy-templates:3.0.7:groovy-templates-3.0.7.jar:f119e07f650ef186ae5a4b944f9e30915b14311bad47c94a6b32de8d4f69bc80',
'org.codehaus.groovy:groovy-test-junit5:3.0.7:groovy-test-junit5-3.0.7.jar:c16eeea07b8e396891e266d7ba9388b24ac804237ffdd9a792b0d08969bad014',
'org.codehaus.groovy:groovy-test:3.0.7:groovy-test-3.0.7.jar:f71afd7c25d43017f89ea47e6de6daec971d159047dae083c1513a8422d44b90',
'org.codehaus.groovy:groovy-testng:3.0.7:groovy-testng-3.0.7.jar:713d5f2231bbb5712aefd362151b9ffd884aeb7ef2e773315cc54259cbdd063d',
'org.codehaus.groovy:groovy-xml:3.0.7:groovy-xml-3.0.7.jar:8a62e7c9ddece3e82676c4bef2f2c100f459602cd1fb6a14e94187bf863e97ff',
'org.codehaus.groovy:groovy:3.0.7:groovy-3.0.7.jar:51d1777e8dd1f00e60ea56e00d8a354ff5aab1f00fc8464ae8d39d71867e401f',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.glassfish.jaxb:jaxb-runtime:2.3.2:jaxb-runtime-2.3.2.jar:e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b',
'org.glassfish.jaxb:txw2:2.3.2:txw2-2.3.2.jar:4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jacoco:org.jacoco.agent:0.8.7:org.jacoco.agent-0.8.7.jar:9cbcc986e0fbe821a78ff1f8f7d5216f200e5eb124e7f6837d1dc4a77b28b143',
'org.jacoco:org.jacoco.ant:0.8.7:org.jacoco.ant-0.8.7.jar:97ca96a382c3f23a44d8eb4c4e6c3742a30cb8005774a76ced0fc4806ce49605',
'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-reflect:1.6.10:kotlin-reflect-1.6.10.jar:3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
'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.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.jacoco:org.jacoco.agent:0.8.3:org.jacoco.agent-0.8.3.jar:522deb254ee16a04cc8341cc8f335f5cb7232982994d961b9cf3a0454709209f',
'org.jacoco:org.jacoco.ant:0.8.3:org.jacoco.ant-0.8.3.jar:735844e1ae15f9b875b42a27ac5cb61cc26e106d9e839e5d1c6756709b424ce0',
'org.jacoco:org.jacoco.core:0.8.3:org.jacoco.core-0.8.3.jar:0818437bc060a0c7cc798148f22b713702aae2771aba104444407697d578f1ea',
'org.jacoco:org.jacoco.report:0.8.3:org.jacoco.report-0.8.3.jar:aae08fa4ff043c807b8876cdb2d8705eb8449a55efce461baa6c09da245088c1',
'org.jetbrains.intellij.deps:trove4j:1.0.20181211:trove4j-1.0.20181211.jar:affb7c85a3c87bdcf69ff1dbb84de11f63dc931293934bc08cd7ab18de083601',
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.junit.jupiter:junit-jupiter-api:5.7.0:junit-jupiter-api-5.7.0.jar:b03f78e0daeed2d77a0af9bcd662b4cdb9693f7ee72e01a539b508b84c63d182',
'org.junit.jupiter:junit-jupiter-engine:5.7.0:junit-jupiter-engine-5.7.0.jar:dfa26af94644ac2612dde6625852fcb550a0d21caa243257de54cba738ba87af',
'org.junit.platform:junit-platform-commons:1.7.0:junit-platform-commons-1.7.0.jar:5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea',
'org.junit.platform:junit-platform-engine:1.7.0:junit-platform-engine-1.7.0.jar:75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2',
'org.junit.platform:junit-platform-launcher:1.7.0:junit-platform-launcher-1.7.0.jar:fbdc748fde4c4279fe1d3c607447cb3b7ccd45d7338fc574f8a894ddf2d16818',
'org.jvnet.staxex:stax-ex:1.8.1:stax-ex-1.8.1.jar:20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-analysis:9.1:asm-analysis-9.1.jar:81a88041b1b8beda5a8a99646098046c48709538270c49def68abff25ac3be34',
'org.ow2.asm:asm-commons:9.1:asm-commons-9.1.jar:afcb26dc1fc12c0c4a99ada670908dd82e18dfc488caf5ee92546996b470c00c',
'org.ow2.asm:asm-tree:9.1:asm-tree-9.1.jar:fd00afa49e9595d7646205b09cecb4a776a8ff0ba06f2d59b8f7bf9c704b4a73',
'org.opentest4j:opentest4j:1.2.0:opentest4j-1.2.0.jar:58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
'org.testng:testng:7.3.0:testng-7.3.0.jar:63727488f9717d57f0d0a0fee5a1fc10a2be9cfcff2ec3a7187656d663c0774e',
'xerces:xercesImpl:2.12.0:xercesImpl-2.12.0.jar:b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0',
'xml-apis:xml-apis:1.4.01:xml-apis-1.4.01.jar:a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad',
]
}

View File

@@ -8,10 +8,9 @@ apply from: 'witness.gradle'
dependencies {
api 'org.briarproject:null-safety:0.1'
api 'com.google.code.findbugs:jsr305:3.0.2'
api 'javax.inject:javax.inject:1'
api "com.google.dagger:dagger:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
testImplementation "junit:junit:$junit_version"
@@ -26,7 +25,7 @@ configurations {
testOutput.extendsFrom(testCompile)
}
task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output, sourceSets.main.output
from sourceSets.test.output
classifier = 'test'
}
artifacts {

View File

@@ -11,6 +11,8 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages();
boolean shouldEnableMailbox();
boolean shouldEnablePrivateGroupsInCore();
boolean shouldEnableForumsInCore();

View File

@@ -16,7 +16,6 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@Immutable
@@ -24,24 +23,17 @@ import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOC
public abstract class BdfMessageValidator implements MessageValidator {
protected static final Logger LOG =
getLogger(BdfMessageValidator.class.getName());
Logger.getLogger(BdfMessageValidator.class.getName());
protected final ClientHelper clientHelper;
protected final MetadataEncoder metadataEncoder;
protected final Clock clock;
protected final boolean canonical;
protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock, boolean canonical) {
this.clientHelper = clientHelper;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
this.canonical = canonical;
}
protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
this(clientHelper, metadataEncoder, clock, true);
this.clientHelper = clientHelper;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
}
protected abstract BdfMessageContext validateMessage(Message m, Group g,
@@ -57,7 +49,7 @@ public abstract class BdfMessageValidator implements MessageValidator {
"Timestamp is too far in the future");
}
try {
BdfList bodyList = clientHelper.toList(m, canonical);
BdfList bodyList = clientHelper.toList(m.getBody());
BdfMessageContext result = validateMessage(m, g, bodyList);
Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies());

View File

@@ -49,9 +49,6 @@ public interface ClientHelper {
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
FormatException;
BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical)
throws DbException, FormatException;
BdfDictionary getGroupMetadataAsDictionary(GroupId g) throws DbException,
FormatException;
@@ -109,8 +106,6 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException;
BdfList toList(Message m, boolean canonical) throws FormatException;
BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, PrivateKey privateKey)

View File

@@ -11,7 +11,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
public final class BdfDictionary extends TreeMap<String, Object> {
public class BdfDictionary extends TreeMap<String, Object> {
public static final Object NULL_VALUE = new Object();
@@ -39,9 +39,9 @@ public final class BdfDictionary extends TreeMap<String, Object> {
}
public Boolean getBoolean(String key) throws FormatException {
Boolean value = getOptionalBoolean(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof Boolean) return (Boolean) o;
throw new FormatException();
}
@Nullable
@@ -52,16 +52,19 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Boolean getBoolean(String key, Boolean defaultValue)
throws FormatException {
Boolean value = getOptionalBoolean(key);
return value == null ? defaultValue : value;
public Boolean getBoolean(String key, Boolean defaultValue) {
Object o = get(key);
if (o instanceof Boolean) return (Boolean) o;
return defaultValue;
}
public Long getLong(String key) throws FormatException {
Long value = getOptionalLong(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
}
@Nullable
@@ -75,37 +78,20 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Long getLong(String key, Long defaultValue) throws FormatException {
Long value = getOptionalLong(key);
return value == null ? defaultValue : value;
}
public Integer getInt(String key) throws FormatException {
Integer value = getOptionalInt(key);
if (value == null) throw new FormatException();
return value;
}
@Nullable
public Integer getOptionalInt(String key) throws FormatException {
Long value = getOptionalLong(key);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
public Integer getInt(String key, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(key);
return value == null ? defaultValue : value;
public Long getLong(String key, Long defaultValue) {
Object o = get(key);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
return defaultValue;
}
public Double getDouble(String key) throws FormatException {
Double value = getOptionalDouble(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
throw new FormatException();
}
@Nullable
@@ -117,16 +103,17 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public Double getDouble(String key, Double defaultValue)
throws FormatException {
Double value = getOptionalDouble(key);
return value == null ? defaultValue : value;
public Double getDouble(String key, Double defaultValue) {
Object o = get(key);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
return defaultValue;
}
public String getString(String key) throws FormatException {
String value = getOptionalString(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof String) return (String) o;
throw new FormatException();
}
@Nullable
@@ -137,16 +124,17 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public String getString(String key, String defaultValue)
throws FormatException {
String value = getOptionalString(key);
return value == null ? defaultValue : value;
public String getString(String key, String defaultValue) {
Object o = get(key);
if (o instanceof String) return (String) o;
return defaultValue;
}
public byte[] getRaw(String key) throws FormatException {
byte[] value = getOptionalRaw(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
throw new FormatException();
}
@Nullable
@@ -158,16 +146,17 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public byte[] getRaw(String key, byte[] defaultValue)
throws FormatException {
byte[] value = getOptionalRaw(key);
return value == null ? defaultValue : value;
public byte[] getRaw(String key, byte[] defaultValue) {
Object o = get(key);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
return defaultValue;
}
public BdfList getList(String key) throws FormatException {
BdfList value = getOptionalList(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof BdfList) return (BdfList) o;
throw new FormatException();
}
@Nullable
@@ -178,16 +167,16 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public BdfList getList(String key, BdfList defaultValue)
throws FormatException {
BdfList value = getOptionalList(key);
return value == null ? defaultValue : value;
public BdfList getList(String key, BdfList defaultValue) {
Object o = get(key);
if (o instanceof BdfList) return (BdfList) o;
return defaultValue;
}
public BdfDictionary getDictionary(String key) throws FormatException {
BdfDictionary value = getOptionalDictionary(key);
if (value == null) throw new FormatException();
return value;
Object o = get(key);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
throw new FormatException();
}
@Nullable
@@ -199,9 +188,9 @@ public final class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException();
}
public BdfDictionary getDictionary(String key, BdfDictionary defaultValue)
throws FormatException {
BdfDictionary value = getOptionalDictionary(key);
return value == null ? defaultValue : value;
public BdfDictionary getDictionary(String key, BdfDictionary defaultValue) {
Object o = get(key);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
return defaultValue;
}
}

View File

@@ -13,7 +13,7 @@ import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
@NotThreadSafe
public final class BdfList extends ArrayList<Object> {
public class BdfList extends ArrayList<Object> {
/**
* Factory method for constructing lists inline.
@@ -33,15 +33,15 @@ public final class BdfList extends ArrayList<Object> {
super(items);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isInRange(int index) {
return index >= 0 && index < size();
}
public Boolean getBoolean(int index) throws FormatException {
Boolean value = getOptionalBoolean(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Boolean) return (Boolean) o;
throw new FormatException();
}
@Nullable
@@ -53,16 +53,21 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Boolean getBoolean(int index, Boolean defaultValue)
throws FormatException {
Boolean value = getOptionalBoolean(index);
return value == null ? defaultValue : value;
public Boolean getBoolean(int index, Boolean defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Boolean) return (Boolean) o;
return defaultValue;
}
public Long getLong(int index) throws FormatException {
Long value = getOptionalLong(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
}
@Nullable
@@ -77,37 +82,22 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Long getLong(int index, Long defaultValue) throws FormatException {
Long value = getOptionalLong(index);
return value == null ? defaultValue : value;
}
public Integer getInt(int index) throws FormatException {
Integer value = getOptionalInt(index);
if (value == null) throw new FormatException();
return value;
}
@Nullable
public Integer getOptionalInt(int index) throws FormatException {
Long value = getOptionalLong(index);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
public Integer getInt(int index, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(index);
return value == null ? defaultValue : value;
public Long getLong(int index, Long defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Long) return (Long) o;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
return defaultValue;
}
public Double getDouble(int index) throws FormatException {
Double value = getOptionalDouble(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
throw new FormatException();
}
@Nullable
@@ -120,16 +110,19 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public Double getDouble(int index, Double defaultValue)
throws FormatException {
Double value = getOptionalDouble(index);
return value == null ? defaultValue : value;
public Double getDouble(int index, Double defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof Double) return (Double) o;
if (o instanceof Float) return ((Float) o).doubleValue();
return defaultValue;
}
public String getString(int index) throws FormatException {
String value = getOptionalString(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof String) return (String) o;
throw new FormatException();
}
@Nullable
@@ -141,16 +134,19 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public String getString(int index, String defaultValue)
throws FormatException {
String value = getOptionalString(index);
return value == null ? defaultValue : value;
public String getString(int index, String defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof String) return (String) o;
return defaultValue;
}
public byte[] getRaw(int index) throws FormatException {
byte[] value = getOptionalRaw(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
throw new FormatException();
}
@Nullable
@@ -163,16 +159,19 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public byte[] getRaw(int index, byte[] defaultValue)
throws FormatException {
byte[] value = getOptionalRaw(index);
return value == null ? defaultValue : value;
public byte[] getRaw(int index, byte[] defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof byte[]) return (byte[]) o;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
return defaultValue;
}
public BdfList getList(int index) throws FormatException {
BdfList value = getOptionalList(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof BdfList) return (BdfList) o;
throw new FormatException();
}
@Nullable
@@ -184,16 +183,18 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public BdfList getList(int index, BdfList defaultValue)
throws FormatException {
BdfList value = getOptionalList(index);
return value == null ? defaultValue : value;
public BdfList getList(int index, BdfList defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof BdfList) return (BdfList) o;
return defaultValue;
}
public BdfDictionary getDictionary(int index) throws FormatException {
BdfDictionary value = getOptionalDictionary(index);
if (value == null) throw new FormatException();
return value;
if (!isInRange(index)) throw new FormatException();
Object o = get(index);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
throw new FormatException();
}
@Nullable
@@ -206,9 +207,10 @@ public final class BdfList extends ArrayList<Object> {
throw new FormatException();
}
public BdfDictionary getDictionary(int index, BdfDictionary defaultValue)
throws FormatException {
BdfDictionary value = getOptionalDictionary(index);
return value == null ? defaultValue : value;
public BdfDictionary getDictionary(int index, BdfDictionary defaultValue) {
if (!isInRange(index)) return defaultValue;
Object o = get(index);
if (o instanceof BdfDictionary) return (BdfDictionary) o;
return defaultValue;
}
}

View File

@@ -32,12 +32,6 @@ public interface BdfReader {
void skipLong() throws IOException;
boolean hasInt() throws IOException;
int readInt() throws IOException;
void skipInt() throws IOException;
boolean hasDouble() throws IOException;
double readDouble() throws IOException;
@@ -60,11 +54,23 @@ public interface BdfReader {
BdfList readList() throws IOException;
void readListStart() throws IOException;
boolean hasListEnd() throws IOException;
void readListEnd() throws IOException;
void skipList() throws IOException;
boolean hasDictionary() throws IOException;
BdfDictionary readDictionary() throws IOException;
void readDictionaryStart() throws IOException;
boolean hasDictionaryEnd() throws IOException;
void readDictionaryEnd() throws IOException;
void skipDictionary() throws IOException;
}

View File

@@ -9,8 +9,6 @@ public interface BdfReaderFactory {
BdfReader createReader(InputStream in);
BdfReader createReader(InputStream in, boolean canonical);
BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize, boolean canonical);
int maxBufferSize);
}

View File

@@ -24,5 +24,13 @@ public interface BdfWriter {
void writeList(Collection<?> c) throws IOException;
void writeListStart() throws IOException;
void writeListEnd() throws IOException;
void writeDictionary(Map<?, ?> m) throws IOException;
void writeDictionaryStart() throws IOException;
void writeDictionaryEnd() throws IOException;
}

View File

@@ -1,26 +1,18 @@
package org.briarproject.bramble.api.keyagreement;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
public interface KeyAgreementConstants {
/**
* The version of the BQP protocol used in beta releases. This version
* number is reserved.
*/
byte BETA_PROTOCOL_VERSION = 89;
/**
* The current version of the BQP protocol.
*/
byte PROTOCOL_VERSION = 4;
/**
* The QR code format identifier, used to distinguish BQP QR codes from
* QR codes used for other purposes. See
* {@link MailboxConstants#QR_FORMAT_ID}.
*/
byte QR_FORMAT_ID = 0;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = PROTOCOL_VERSION;
/**
* The length of the BQP key commitment in bytes.
*/

View File

@@ -7,5 +7,5 @@ import java.io.IOException;
@NotNullByDefault
public interface PayloadParser {
Payload parse(String payload) throws IOException;
Payload parse(byte[] raw) throws IOException;
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List;
@@ -20,18 +19,6 @@ public interface MailboxConstants {
*/
TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
/**
* The QR code format identifier, used to distinguish mailbox QR codes
* from QR codes used for other purposes. See
* {@link KeyAgreementConstants#QR_FORMAT_ID};
*/
byte QR_FORMAT_ID = 1;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = 0;
/**
* Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}.

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -35,16 +34,6 @@ public interface MailboxManager {
*/
MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Takes a textual QR code representation in
* {@link org.briarproject.bramble.util.Base32} format and converts it
* into a qrCodePayload as expected by {@link #startPairingTask(String)}.
*
* @throws FormatException when the provided payload did not include a
* proper briar-mailbox:// link.
*/
String convertBase32Payload(String base32Payload) throws FormatException;
/**
* Can be used by the UI to test the mailbox connection.
*

View File

@@ -1,44 +1,17 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
public abstract class MailboxPairingState {
public abstract static class Pending extends MailboxPairingState {
public final long timeStarted;
private Pending(long timeStarted) {
this.timeStarted = timeStarted;
}
public static class QrCodeReceived extends MailboxPairingState {
}
public static class QrCodeReceived extends Pending {
public QrCodeReceived(long timeStarted) {
super(timeStarted);
}
}
public static class Pairing extends Pending {
public Pairing(long timeStarted) {
super(timeStarted);
}
public static class Pairing extends MailboxPairingState {
}
public static class Paired extends MailboxPairingState {
}
public static class InvalidQrCode extends MailboxPairingState {
public final QrCodeType qrCodeType;
public final int formatVersion;
public InvalidQrCode(QrCodeType qrCodeType, int formatVersion) {
this.qrCodeType = qrCodeType;
this.formatVersion = formatVersion;
}
}
public static class MailboxAlreadyPaired extends MailboxPairingState {

View File

@@ -27,6 +27,8 @@ public interface TorConstants {
int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3;
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface QrCodeClassifier {
enum QrCodeType {
BQP,
MAILBOX,
UNKNOWN
}
Pair<QrCodeType, Integer> classifyQrCode(String payload);
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Thrown when a QR code that has been scanned does not have the expected type.
*/
@Immutable
@NotNullByDefault
public class WrongQrCodeTypeException extends FormatException {
private final QrCodeType qrCodeType;
public WrongQrCodeTypeException(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
public QrCodeType getQrCodeType() {
return qrCodeType;
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface LocationUtils {
/**
* Get the country the device is currently located in, or "" if it cannot
* be determined.
* <p>
* The country codes are formatted upper-case and as per <a href="
* https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2</a>.
*/
String getCurrentCountry();
}

View File

@@ -4,8 +4,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.startsWithIgnoreCase;
@NotNullByDefault
public class OsUtils {
@@ -15,15 +13,15 @@ public class OsUtils {
private static final String vendor = System.getProperty("java.vendor");
public static boolean isWindows() {
return os != null && startsWithIgnoreCase(os, "Win");
return os != null && os.contains("Windows");
}
public static boolean isMac() {
return os != null && os.equalsIgnoreCase("Mac OS X");
return os != null && os.contains("Mac OS");
}
public static boolean isLinux() {
return os != null && startsWithIgnoreCase(os, "Linux") && !isAndroid();
return os != null && os.contains("Linux") && !isAndroid();
}
public static boolean isAndroid() {

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
@@ -14,21 +15,15 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.nio.charset.CodingErrorAction.REPORT;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed")
@NotNullByDefault
public class StringUtils {
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final Charset US_ASCII = Charset.forName("US-ASCII");
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final Pattern MAC =
Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7',
@@ -50,41 +45,33 @@ public class StringUtils {
}
public static byte[] toUtf8(String s) {
return s.getBytes(UTF_8);
try {
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
public static String fromUtf8(byte[] bytes) throws FormatException {
return fromUtf8(bytes, 0, bytes.length, true);
public static String fromUtf8(byte[] bytes) {
return fromUtf8(bytes, 0, bytes.length);
}
public static String fromUtf8(byte[] bytes, int off, int len)
throws FormatException {
return fromUtf8(bytes, off, len, true);
}
private static String fromUtf8(byte[] bytes, int off, int len,
boolean strict) throws FormatException {
public static String fromUtf8(byte[] bytes, int off, int len) {
CharsetDecoder decoder = UTF_8.newDecoder();
decoder.onMalformedInput(strict ? REPORT : IGNORE);
decoder.onUnmappableCharacter(strict ? REPORT : IGNORE);
decoder.onMalformedInput(IGNORE);
decoder.onUnmappableCharacter(IGNORE);
ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len);
try {
return decoder.decode(buffer).toString();
} catch (CharacterCodingException e) {
throw new FormatException();
throw new AssertionError(e);
}
}
public static String truncateUtf8(String s, int maxUtf8Length) {
byte[] utf8 = toUtf8(s);
if (utf8.length <= maxUtf8Length) return s;
// Don't be strict when converting back, so that if we truncate a
// multi-byte character the whole character gets dropped
try {
return fromUtf8(utf8, 0, maxUtf8Length, false);
} catch (FormatException e) {
throw new AssertionError(e);
}
return fromUtf8(utf8, 0, maxUtf8Length);
}
/**
@@ -176,9 +163,4 @@ public class StringUtils {
}
return new String(c);
}
// see https://stackoverflow.com/a/38947571
static boolean startsWithIgnoreCase(String s, String prefix) {
return s.regionMatches(true, 0, prefix, 0, prefix.length());
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
@@ -9,12 +8,9 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.Map.Entry;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class BdfDictionaryTest extends BrambleTestCase {
@@ -23,20 +19,20 @@ public class BdfDictionaryTest extends BrambleTestCase {
public void testConstructors() {
assertEquals(Collections.<String, Object>emptyMap(),
new BdfDictionary());
assertEquals(singletonMap("foo", NULL_VALUE),
new BdfDictionary(singletonMap("foo", NULL_VALUE)));
assertEquals(Collections.singletonMap("foo", NULL_VALUE),
new BdfDictionary(Collections.singletonMap("foo", NULL_VALUE)));
}
@Test
public void testFactoryMethod() {
assertEquals(Collections.<String, Object>emptyMap(),
BdfDictionary.of());
assertEquals(singletonMap("foo", NULL_VALUE),
assertEquals(Collections.singletonMap("foo", NULL_VALUE),
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)));
}
@Test
public void testLongPromotion() throws Exception {
public void testIntegerPromotion() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1);
d.put("bar", (short) 2);
@@ -48,33 +44,6 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), d.getLong("bam"));
}
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1);
d.put("bar", (short) 2);
d.put("baz", 3);
d.put("bam", 4L);
assertEquals(Integer.valueOf(1), d.getInt("foo"));
assertEquals(Integer.valueOf(2), d.getInt("bar"));
assertEquals(Integer.valueOf(3), d.getInt("baz"));
assertEquals(Integer.valueOf(4), d.getInt("bam"));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MIN_VALUE - 1L));
d.getInt("foo");
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MAX_VALUE + 1L));
d.getInt("foo");
}
@Test
public void testFloatPromotion() throws Exception {
BdfDictionary d = new BdfDictionary();
@@ -98,7 +67,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testKeySetIteratorIsOrderedByKeys() {
public void testKeySetIteratorIsOrderedByKeys() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -117,7 +86,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testValuesIteratorIsOrderedByKeys() {
public void testValuesIteratorIsOrderedByKeys() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -136,7 +105,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
}
@Test
public void testEntrySetIteratorIsOrderedByKeys() {
public void testEntrySetIteratorIsOrderedByKeys() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("a", 1);
d.put("d", 4);
@@ -161,284 +130,4 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals("d", e.getKey());
assertEquals(4, e.getValue());
}
@Test(expected = FormatException.class)
public void testMissingValueForBooleanThrowsFormatException()
throws Exception {
new BdfDictionary().getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForLongThrowsFormatException()
throws Exception {
new BdfDictionary().getLong("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForIntThrowsFormatException() throws Exception {
new BdfDictionary().getInt("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDoubleThrowsFormatException()
throws Exception {
new BdfDictionary().getDouble("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForStringThrowsFormatException()
throws Exception {
new BdfDictionary().getString("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForRawThrowsFormatException() throws Exception {
new BdfDictionary().getRaw("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForListThrowsFormatException()
throws Exception {
new BdfDictionary().getList("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDictionaryThrowsFormatException()
throws Exception {
new BdfDictionary().getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getString("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getList("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDictionary("foo");
}
@Test
public void testOptionalMethodsReturnNullForMissingValue()
throws Exception {
testOptionalMethodsReturnNull(new BdfDictionary());
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testOptionalMethodsReturnNull(d);
}
private void testOptionalMethodsReturnNull(BdfDictionary d)
throws Exception {
assertNull(d.getOptionalBoolean("foo"));
assertNull(d.getOptionalLong("foo"));
assertNull(d.getOptionalInt("foo"));
assertNull(d.getOptionalDouble("foo"));
assertNull(d.getOptionalString("foo"));
assertNull(d.getOptionalRaw("foo"));
assertNull(d.getOptionalList("foo"));
assertNull(d.getOptionalDictionary("foo"));
}
@Test
public void testDefaultMethodsReturnDefaultForMissingValue()
throws Exception {
testDefaultMethodsReturnDefault(new BdfDictionary());
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testDefaultMethodsReturnDefault(d);
}
private void testDefaultMethodsReturnDefault(BdfDictionary d)
throws Exception {
assertEquals(TRUE, d.getBoolean("foo", TRUE));
assertEquals(Long.valueOf(123L), d.getLong("foo", 123L));
assertEquals(Integer.valueOf(123), d.getInt("foo", 123));
assertEquals(Double.valueOf(123D), d.getDouble("foo", 123D));
assertEquals("123", d.getString("foo", "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, d.getRaw("foo", defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, d.getList("foo", defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, d.getDictionary("foo", defaultDict));
}
@Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo", true);
}
@Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo", 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo", 1);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo", 1D);
}
@Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo", "");
}
@Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo", new byte[0]);
}
@Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo",
new BdfList());
}
@Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo",
new BdfDictionary());
}
}

View File

@@ -5,31 +5,31 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import java.util.Arrays;
import java.util.Collections;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BdfListTest extends BrambleTestCase {
@Test
public void testConstructors() {
assertEquals(emptyList(), new BdfList());
assertEquals(asList(1, 2, NULL_VALUE),
new BdfList(asList(1, 2, NULL_VALUE)));
assertEquals(Collections.emptyList(), new BdfList());
assertEquals(Arrays.asList(1, 2, NULL_VALUE),
new BdfList(Arrays.asList(1, 2, NULL_VALUE)));
}
@Test
public void testFactoryMethod() {
assertEquals(emptyList(), BdfList.of());
assertEquals(asList(1, 2, NULL_VALUE), BdfList.of(1, 2, NULL_VALUE));
assertEquals(Collections.emptyList(), BdfList.of());
assertEquals(Arrays.asList(1, 2, NULL_VALUE),
BdfList.of(1, 2, NULL_VALUE));
}
@Test
public void testLongPromotion() throws Exception {
public void testIntegerPromotion() throws Exception {
BdfList list = new BdfList();
list.add((byte) 1);
list.add((short) 2);
@@ -41,31 +41,6 @@ public class BdfListTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), list.getLong(3));
}
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfList list = new BdfList();
list.add((byte) 1);
list.add((short) 2);
list.add(3);
list.add(4L);
assertEquals(Integer.valueOf(1), list.getInt(0));
assertEquals(Integer.valueOf(2), list.getInt(1));
assertEquals(Integer.valueOf(3), list.getInt(2));
assertEquals(Integer.valueOf(4), list.getInt(3));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfList list = BdfList.of(Integer.MIN_VALUE - 1L);
list.getInt(0);
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfList list = BdfList.of(Integer.MAX_VALUE + 1L);
list.getInt(0);
}
@Test
public void testFloatPromotion() throws Exception {
BdfList list = new BdfList();
@@ -88,6 +63,61 @@ public class BdfListTest extends BrambleTestCase {
assertArrayEquals(new byte[123], second);
}
@Test
@SuppressWarnings("ConstantConditions")
public void testIndexOutOfBoundsReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(-1, defaultBoolean));
assertEquals(defaultBoolean, list.getBoolean(3, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(-1, defaultLong));
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(-1, defaultDouble));
assertEquals(defaultDouble, list.getDouble(3, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(-1, defaultString));
assertEquals(defaultString, list.getString(3, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(-1, defaultBytes));
assertArrayEquals(defaultBytes, list.getRaw(3, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(-1, defaultList));
assertEquals(defaultList, list.getList(3, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(-1, defaultDict));
assertEquals(defaultDict, list.getDictionary(3, defaultDict));
}
@Test
@SuppressWarnings("ConstantConditions")
public void testWrongTypeReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3, true);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(0, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(0, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(0, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(0, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
@Test(expected = FormatException.class)
public void testNegativeIndexForBooleanThrowsFormatException()
throws Exception {
@@ -100,12 +130,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(-1, true);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForLongThrowsFormatException()
throws Exception {
@@ -118,30 +142,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(-1, 1L);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1, 1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDoubleThrowsFormatException()
throws Exception {
@@ -154,12 +154,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(-1, 1D);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForStringThrowsFormatException()
throws Exception {
@@ -172,12 +166,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(-1, "");
}
@Test(expected = FormatException.class)
public void testNegativeIndexForRawThrowsFormatException()
throws Exception {
@@ -190,12 +178,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(-1, new byte[0]);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForListThrowsFormatException()
throws Exception {
@@ -208,11 +190,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(-1, new BdfList());
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDictionaryThrowsFormatException()
@@ -226,12 +203,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDictionary(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(-1, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForBooleanThrowsFormatException()
throws Exception {
@@ -244,12 +215,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(0, true);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForLongThrowsFormatException()
throws Exception {
@@ -262,30 +227,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0, 1);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDoubleThrowsFormatException()
throws Exception {
@@ -298,12 +239,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(0, 1D);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForStringThrowsFormatException()
throws Exception {
@@ -316,12 +251,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(0, "");
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForRawThrowsFormatException()
throws Exception {
@@ -334,12 +263,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForListThrowsFormatException()
throws Exception {
@@ -352,11 +275,6 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(0, new BdfList());
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDictionaryThrowsFormatException()
@@ -369,13 +287,6 @@ public class BdfListTest extends BrambleTestCase {
throws Exception {
new BdfList().getOptionalDictionary(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException()
throws Exception {
@@ -388,12 +299,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalBoolean(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfList.of(123).getBoolean(0, true);
}
@Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfList.of(1.23).getLong(0);
@@ -405,29 +310,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(1.23).getOptionalLong(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfList.of(1.23).getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfList.of(1.23).getInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getInt(0, 1);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfList.of(123).getDouble(0);
@@ -439,12 +321,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalDouble(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfList.of(123).getDouble(0, 1D);
}
@Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfList.of(123).getString(0);
@@ -456,12 +332,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalString(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfList.of(123).getString(0, "");
}
@Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfList.of(123).getRaw(0);
@@ -473,12 +343,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalRaw(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfList.of(123).getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException() throws Exception {
BdfList.of(123).getList(0);
@@ -490,11 +354,6 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalList(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfList.of(123).getList(0, new BdfList());
}
@Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException()
@@ -507,81 +366,4 @@ public class BdfListTest extends BrambleTestCase {
throws Exception {
BdfList.of(123).getOptionalDictionary(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfList.of(123).getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getBoolean(0);
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getLong(0);
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getInt(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getDouble(0);
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getString(0);
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getRaw(0);
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getList(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getDictionary(0);
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertNull(list.getOptionalBoolean(0));
assertNull(list.getOptionalLong(0));
assertNull(list.getOptionalInt(0));
assertNull(list.getOptionalDouble(0));
assertNull(list.getOptionalString(0));
assertNull(list.getOptionalRaw(0));
assertNull(list.getOptionalList(0));
assertNull(list.getOptionalDictionary(0));
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertEquals(TRUE, list.getBoolean(0, TRUE));
assertEquals(Long.valueOf(123L), list.getLong(0, 123L));
assertEquals(Integer.valueOf(123), list.getInt(0, 123));
assertEquals(Double.valueOf(123D), list.getDouble(0, 123D));
assertEquals("123", list.getString(0, "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, list.getRaw(0, defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
}

View File

@@ -1,10 +1,10 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.fasterxml.jackson.core:jackson-annotations:2.13.4:jackson-annotations-2.13.4.jar:ac5b27a634942391ca113850ee7db01df1499a240174021263501c05fc653b44',
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.45:dagger-2.45.jar:f011cae7d2c0fb7ea17c34e05bc10e768b1081a5892ad019cf1fdb0e125c49c1',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
@@ -12,8 +12,8 @@ dependencyVerification {
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.22:animal-sniffer-ant-tasks-1.22.jar:3f6afeb3e09301d2d7179ed1db21e3ad8846c1e38415ad832a395138ae3f4218',
'org.codehaus.mojo:animal-sniffer:1.22:animal-sniffer-1.22.jar:f18c11a25bdd8b520b9c6a28cbb6f33007c812ab0051b6be3f0778e660aa501c',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
@@ -24,6 +24,6 @@ dependencyVerification {
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.3:asm-9.3.jar:1263369b59e29c943918de11d6d6152e2ec6085ce63e5710516f8c67d368e4bc',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
]
}

View File

@@ -9,31 +9,30 @@ apply from: 'witness.gradle'
apply from: '../dagger.gradle'
dependencies {
api project(':bramble-api')
api "org.briarproject:onionwrapper-core:$onionwrapper_version"
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
implementation project(path: ':bramble-api', configuration: 'default')
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
//noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.5'
implementation 'org.briarproject:socks-socket:0.1'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'net.jodah:concurrentunit:0.4.6'
testImplementation 'net.jodah:concurrentunit:0.4.2'
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -53,7 +52,7 @@ configurations {
testOutput.extendsFrom(testCompile)
}
task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output, sourceSets.main.output
from sourceSets.test.output
classifier = 'test'
}
artifacts {

View File

@@ -17,7 +17,6 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.qrcode.QrCodeModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -48,7 +47,6 @@ import dagger.Module;
MailboxModule.class,
PluginModule.class,
PropertiesModule.class,
QrCodeModule.class,
RecordModule.class,
ReliabilityModule.class,
RendezvousModule.class,

View File

@@ -4,7 +4,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -20,10 +19,9 @@ public class TimeLoggingExecutor extends ThreadPoolExecutor {
public TimeLoggingExecutor(String tag, int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler);
handler);
log = Logger.getLogger(tag);
}

View File

@@ -19,6 +19,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -28,7 +29,6 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager {
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), UTF_8));
new FileInputStream(f), Charset.forName("UTF-8")));
String key = reader.readLine();
reader.close();
return key;
@@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(UTF_8));
out.write(key.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
}

View File

@@ -155,13 +155,7 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m)
throws DbException, FormatException {
return getMessageAsList(txn, m, true);
}
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m,
boolean canonical) throws DbException, FormatException {
return toList(db.getMessage(txn, m), canonical);
return toList(db.getMessage(txn, m).getBody());
}
@Override
@@ -319,13 +313,8 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList toList(byte[] b, int off, int len) throws FormatException {
return toList(b, off, len, true);
}
private BdfList toList(byte[] b, int off, int len, boolean canonical)
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
BdfReader reader = bdfReaderFactory.createReader(in, canonical);
BdfReader reader = bdfReaderFactory.createReader(in);
try {
BdfList list = reader.readList();
if (!reader.eof()) throw new FormatException();
@@ -339,7 +328,7 @@ class ClientHelperImpl implements ClientHelper {
@Override
public BdfList toList(byte[] b) throws FormatException {
return toList(b, 0, b.length, true);
return toList(b, 0, b.length);
}
@Override
@@ -347,12 +336,6 @@ class ClientHelperImpl implements ClientHelper {
return toList(m.getBody());
}
@Override
public BdfList toList(Message m, boolean canonical) throws FormatException {
byte[] b = m.getBody();
return toList(b, 0, b.length, canonical);
}
@Override
public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
@@ -378,7 +361,7 @@ class ClientHelperImpl implements ClientHelper {
public Author parseAndValidateAuthor(BdfList author)
throws FormatException {
checkSize(author, 3);
int formatVersion = author.getInt(0);
int formatVersion = author.getLong(0).intValue();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
@@ -489,7 +472,8 @@ class ClientHelperImpl implements ClientHelper {
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getInt(0), element.getInt(1)));
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
}
// Sort the list of versions for easier comparison
sort(list);
@@ -502,7 +486,7 @@ class ClientHelperImpl implements ClientHelper {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getInt(GROUP_KEY_CONTACT_ID));
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}

View File

@@ -29,6 +29,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
@@ -50,7 +51,6 @@ import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHE
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@NotNullByDefault
class CryptoComponentImpl implements CryptoComponent {
@@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(US_ASCII);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION);

View File

@@ -9,7 +9,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Inject;
@@ -38,31 +37,31 @@ public class CryptoExecutorModule {
private static final int MAX_EXECUTOR_THREADS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
public CryptoExecutorModule() {
}
private final ExecutorService cryptoExecutor;
@Provides
@Singleton
@CryptoExecutor
ExecutorService provideCryptoExecutorService(
LifecycleManager lifecycleManager, ThreadFactory threadFactory) {
public CryptoExecutorModule() {
// Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
ExecutorService cryptoExecutor = new TimeLoggingExecutor(
"CryptoExecutor", 0, MAX_EXECUTOR_THREADS, 60, SECONDS, queue,
threadFactory, policy);
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
}
@Provides
@Singleton
@CryptoExecutor
ExecutorService provideCryptoExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
@Provides
@CryptoExecutor
Executor provideCryptoExecutor(
@CryptoExecutor ExecutorService cryptoExecutor) {
Executor provideCryptoExecutor() {
return cryptoExecutor;
}
}

View File

@@ -39,13 +39,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Scanner;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@Immutable
@NotNullByDefault
public class MessageEncrypter {
@@ -229,7 +228,7 @@ public class MessageEncrypter {
PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in);
byte[] plaintext = message.getBytes(UTF_8);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8"));
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
}
@@ -243,7 +242,7 @@ public class MessageEncrypter {
encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, UTF_8));
System.out.println(new String(plaintext, Charset.forName("UTF-8")));
}
private static String readFully(InputStream in) throws IOException {

View File

@@ -18,18 +18,12 @@ class BdfReaderFactoryImpl implements BdfReaderFactory {
@Override
public BdfReader createReader(InputStream in) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, true);
}
@Override
public BdfReader createReader(InputStream in, boolean canonical) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, canonical);
DEFAULT_MAX_BUFFER_SIZE);
}
@Override
public BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize, boolean canonical) {
return new BdfReaderImpl(in, nestedLimit, maxBufferSize, canonical);
int maxBufferSize) {
return new BdfReaderImpl(in, nestedLimit, maxBufferSize);
}
}

View File

@@ -33,24 +33,21 @@ import static org.briarproject.bramble.util.StringUtils.fromUtf8;
@NotThreadSafe
@NotNullByDefault
final class BdfReaderImpl implements BdfReader {
class BdfReaderImpl implements BdfReader {
private static final byte[] EMPTY_BUFFER = new byte[0];
private final InputStream in;
private final int nestedLimit, maxBufferSize;
private final boolean canonical;
private boolean hasLookahead = false, eof = false;
private byte next;
private byte[] buf = new byte[8];
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize,
boolean canonical) {
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize) {
this.in = in;
this.nestedLimit = nestedLimit;
this.maxBufferSize = maxBufferSize;
this.canonical = canonical;
}
private void readLookahead() throws IOException {
@@ -191,22 +188,13 @@ final class BdfReaderImpl implements BdfReader {
private short readInt16() throws IOException {
readIntoBuffer(2);
short value = (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
if (canonical && value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
// Value could have been encoded as an INT_8
throw new FormatException();
}
return value;
return (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
}
private int readInt32() throws IOException {
readIntoBuffer(4);
int value = 0;
for (int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
if (canonical && value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
// Value could have been encoded as an INT_16
throw new FormatException();
}
return value;
}
@@ -214,11 +202,6 @@ final class BdfReaderImpl implements BdfReader {
readIntoBuffer(8);
long value = 0;
for (int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
if (canonical && value >= Integer.MIN_VALUE &&
value <= Integer.MAX_VALUE) {
// Value could have been encoded as an INT_32
throw new FormatException();
}
return value;
}
@@ -232,31 +215,6 @@ final class BdfReaderImpl implements BdfReader {
hasLookahead = false;
}
@Override
public boolean hasInt() throws IOException {
if (!hasLookahead) readLookahead();
if (eof) return false;
return next == INT_8 || next == INT_16 || next == INT_32;
}
@Override
public int readInt() throws IOException {
if (!hasInt()) throw new FormatException();
hasLookahead = false;
if (next == INT_8) return readInt8();
if (next == INT_16) return readInt16();
return readInt32();
}
@Override
public void skipInt() throws IOException {
if (!hasInt()) throw new FormatException();
if (next == INT_8) skip(1);
else if (next == INT_16) skip(2);
else skip(4);
hasLookahead = false;
}
@Override
public boolean hasDouble() throws IOException {
if (!hasLookahead) readLookahead();
@@ -365,19 +323,35 @@ final class BdfReaderImpl implements BdfReader {
private BdfList readList(int level) throws IOException {
if (!hasList()) throw new FormatException();
if (level > nestedLimit) throw new FormatException();
hasLookahead = false;
BdfList list = new BdfList();
while (!hasEnd()) list.add(readObject(level + 1));
readEnd();
readListStart();
while (!hasListEnd()) list.add(readObject(level + 1));
readListEnd();
return list;
}
@Override
public void readListStart() throws IOException {
if (!hasList()) throw new FormatException();
hasLookahead = false;
}
@Override
public boolean hasListEnd() throws IOException {
return hasEnd();
}
private boolean hasEnd() throws IOException {
if (!hasLookahead) readLookahead();
if (eof) return false;
return next == END;
}
@Override
public void readListEnd() throws IOException {
readEnd();
}
private void readEnd() throws IOException {
if (!hasEnd()) throw new FormatException();
hasLookahead = false;
@@ -387,7 +361,7 @@ final class BdfReaderImpl implements BdfReader {
public void skipList() throws IOException {
if (!hasList()) throw new FormatException();
hasLookahead = false;
while (!hasEnd()) skipObject();
while (!hasListEnd()) skipObject();
hasLookahead = false;
}
@@ -406,27 +380,35 @@ final class BdfReaderImpl implements BdfReader {
private BdfDictionary readDictionary(int level) throws IOException {
if (!hasDictionary()) throw new FormatException();
if (level > nestedLimit) throw new FormatException();
hasLookahead = false;
BdfDictionary dictionary = new BdfDictionary();
String prevKey = null;
while (!hasEnd()) {
String key = readString();
if (canonical && prevKey != null && key.compareTo(prevKey) <= 0) {
// Keys not unique and sorted
throw new FormatException();
}
dictionary.put(key, readObject(level + 1));
prevKey = key;
}
readEnd();
readDictionaryStart();
while (!hasDictionaryEnd())
dictionary.put(readString(), readObject(level + 1));
readDictionaryEnd();
return dictionary;
}
@Override
public void readDictionaryStart() throws IOException {
if (!hasDictionary()) throw new FormatException();
hasLookahead = false;
}
@Override
public boolean hasDictionaryEnd() throws IOException {
return hasEnd();
}
@Override
public void readDictionaryEnd() throws IOException {
readEnd();
}
@Override
public void skipDictionary() throws IOException {
if (!hasDictionary()) throw new FormatException();
hasLookahead = false;
while (!hasEnd()) {
while (!hasDictionaryEnd()) {
skipString();
skipObject();
}

View File

@@ -2,13 +2,11 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -17,7 +15,6 @@ import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.data.Types.DICTIONARY;
import static org.briarproject.bramble.data.Types.END;
@@ -36,11 +33,10 @@ import static org.briarproject.bramble.data.Types.STRING_16;
import static org.briarproject.bramble.data.Types.STRING_32;
import static org.briarproject.bramble.data.Types.STRING_8;
import static org.briarproject.bramble.data.Types.TRUE;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@NotThreadSafe
@NotNullByDefault
final class BdfWriterImpl implements BdfWriter {
class BdfWriterImpl implements BdfWriter {
private final OutputStream out;
@@ -117,7 +113,7 @@ final class BdfWriterImpl implements BdfWriter {
@Override
public void writeString(String s) throws IOException {
byte[] b = s.getBytes(UTF_8);
byte[] b = s.getBytes("UTF-8");
if (b.length <= Byte.MAX_VALUE) {
out.write(STRING_8);
out.write((byte) b.length);
@@ -165,33 +161,39 @@ final class BdfWriterImpl implements BdfWriter {
else if (o instanceof String) writeString((String) o);
else if (o instanceof byte[]) writeRaw((byte[]) o);
else if (o instanceof Bytes) writeRaw(((Bytes) o).getBytes());
else if (o instanceof List) writeList((List<?>) o);
else if (o instanceof Map) writeDictionary((Map<?, ?>) o);
else if (o instanceof List) writeList((List) o);
else if (o instanceof Map) writeDictionary((Map) o);
else throw new FormatException();
}
@Override
public void writeListStart() throws IOException {
out.write(LIST);
}
@Override
public void writeListEnd() throws IOException {
out.write(END);
}
@Override
public void writeDictionary(Map<?, ?> m) throws IOException {
out.write(DICTIONARY);
if (m instanceof BdfDictionary) {
// Entries are already sorted and keys are known to be strings
for (Entry<String, Object> e : ((BdfDictionary) m).entrySet()) {
writeString(e.getKey());
writeObject(e.getValue());
}
} else {
// Check that keys are strings, write entries in canonical order
List<String> keys = new ArrayList<>(m.size());
for (Object k : m.keySet()) {
if (!(k instanceof String)) throw new FormatException();
keys.add((String) k);
}
sort(keys);
for (String key : keys) {
writeString(key);
writeObject(m.get(key));
}
for (Entry<?, ?> e : m.entrySet()) {
if (!(e.getKey() instanceof String)) throw new FormatException();
writeString((String) e.getKey());
writeObject(e.getValue());
}
out.write(END);
}
@Override
public void writeDictionaryStart() throws IOException {
out.write(DICTIONARY);
}
@Override
public void writeDictionaryEnd() throws IOException {
out.write(END);
}
}

View File

@@ -9,7 +9,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Inject;
@@ -29,20 +28,24 @@ public class DatabaseExecutorModule {
ExecutorService executorService;
}
@Provides
@Singleton
@DatabaseExecutor
ExecutorService provideDatabaseExecutorService(
LifecycleManager lifecycleManager, ThreadFactory threadFactory) {
private final ExecutorService databaseExecutor;
public DatabaseExecutorModule() {
// Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Use a single thread and keep it in the pool for 60 secs
ExecutorService databaseExecutor = new TimeLoggingExecutor(
"DatabaseExecutor", 0, 1, 60, SECONDS, queue, threadFactory,
policy);
databaseExecutor = new TimeLoggingExecutor("DatabaseExecutor", 0, 1,
60, SECONDS, queue, policy);
}
@Provides
@Singleton
@DatabaseExecutor
ExecutorService provideDatabaseExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(databaseExecutor);
return databaseExecutor;
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.event;
import org.briarproject.bramble.api.event.EventExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import javax.inject.Singleton;
@@ -23,11 +22,10 @@ public class DefaultEventExecutorModule {
@Provides
@Singleton
@EventExecutor
Executor provideEventExecutor(ThreadFactory threadFactory) {
Executor provideEventExecutor() {
return newSingleThreadExecutor(r -> {
Thread t = threadFactory.newThread(r);
Thread t = new Thread(r);
t.setDaemon(true);
t.setName(t.getName() + "-Event");
return t;
});
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -17,13 +19,13 @@ public class KeyAgreementModule {
}
@Provides
PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) {
return payloadEncoder;
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) {
return new PayloadEncoderImpl(bdfWriterFactory);
}
@Provides
PayloadParser providePayloadParser(PayloadParserImpl payloadParser) {
return payloadParser;
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory);
}
@Provides

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.Payload;
@@ -14,8 +13,7 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
@Immutable
@NotNullByDefault
@@ -31,16 +29,14 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override
public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
out.write(formatIdAndVersion);
BdfList payload = new BdfList();
payload.add(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors()) {
payload.add(d.getDescriptor());
}
out.write(PROTOCOL_VERSION);
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeList(payload);
w.writeListStart(); // Payload start
w.writeRaw(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors())
w.writeList(d.getDescriptor());
w.writeListEnd(); // Payload end
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new AssertionError(e);

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
@@ -12,9 +11,6 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.ByteArrayInputStream;
@@ -25,42 +21,34 @@ import java.util.List;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory;
private final QrCodeClassifier qrCodeClassifier;
@Inject
PayloadParserImpl(BdfReaderFactory bdfReaderFactory,
QrCodeClassifier qrCodeClassifier) {
PayloadParserImpl(BdfReaderFactory bdfReaderFactory) {
this.bdfReaderFactory = bdfReaderFactory;
this.qrCodeClassifier = qrCodeClassifier;
}
@Override
public Payload parse(String payloadString) throws IOException {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payloadString);
QrCodeType qrCodeType = typeAndVersion.getFirst();
if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType);
int formatVersion = typeAndVersion.getSecond();
if (formatVersion != QR_FORMAT_VERSION) {
boolean tooOld = formatVersion < QR_FORMAT_VERSION;
public Payload parse(byte[] raw) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the protocol version
int protocolVersion = in.read();
if (protocolVersion == -1) throw new FormatException();
if (protocolVersion != PROTOCOL_VERSION) {
boolean tooOld = protocolVersion < PROTOCOL_VERSION ||
protocolVersion == BETA_PROTOCOL_VERSION;
throw new UnsupportedVersionException(tooOld);
}
byte[] raw = payloadString.getBytes(ISO_8859_1);
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the format identifier and version (already parsed)
if (in.read() == -1) throw new AssertionError();
// The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in);
BdfList payload = r.readList();
@@ -73,7 +61,7 @@ class PayloadParserImpl implements PayloadParser {
List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 1; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i);
int transportId = descriptor.getInt(0);
long transportId = descriptor.getLong(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) {
TransportId id = BluetoothConstants.ID;
recognised.add(new TransportDescriptor(id, descriptor));

View File

@@ -9,7 +9,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Inject;
@@ -29,6 +28,19 @@ public class LifecycleModule {
Executor executor;
}
private final ExecutorService ioExecutor;
public LifecycleModule() {
// The thread pool is unbounded, so use direct handoff
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create threads as required and keep them in the pool for 60 seconds
ioExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, SECONDS, queue, policy);
}
@Provides
@Singleton
ShutdownManager provideShutdownManager() {
@@ -45,16 +57,7 @@ public class LifecycleModule {
@Provides
@Singleton
@IoExecutor
Executor provideIoExecutor(LifecycleManager lifecycleManager,
ThreadFactory threadFactory) {
// The thread pool is unbounded, so use direct handoff
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create threads as required and keep them in the pool for 60 seconds
ExecutorService ioExecutor = new ThreadPoolExecutor(0,
Integer.MAX_VALUE, 60, SECONDS, queue, threadFactory, policy);
Executor provideIoExecutor(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(ioExecutor);
return ioExecutor;
}

View File

@@ -20,15 +20,12 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
class MailboxApiCallerImpl implements MailboxApiCaller {
private final TaskScheduler taskScheduler;
private final MailboxConfig mailboxConfig;
private final Executor ioExecutor;
@Inject
MailboxApiCallerImpl(TaskScheduler taskScheduler,
MailboxConfig mailboxConfig,
@IoExecutor Executor ioExecutor) {
this.taskScheduler = taskScheduler;
this.mailboxConfig = mailboxConfig;
this.ioExecutor = ioExecutor;
}
@@ -52,8 +49,7 @@ class MailboxApiCallerImpl implements MailboxApiCaller {
private boolean cancelled = false;
@GuardedBy("lock")
private long retryIntervalMs =
mailboxConfig.getApiCallerMinRetryInterval();
private long retryIntervalMs = MIN_RETRY_INTERVAL_MS;
private Task(ApiCall apiCall) {
this.apiCall = apiCall;
@@ -78,9 +74,8 @@ class MailboxApiCallerImpl implements MailboxApiCaller {
scheduledTask = taskScheduler.schedule(this::callApi,
ioExecutor, retryIntervalMs, MILLISECONDS);
// Increase the retry interval each time we retry
retryIntervalMs = min(
mailboxConfig.getApiCallerMaxRetryInterval(),
retryIntervalMs * 2);
retryIntervalMs =
min(MAX_RETRY_INTERVAL_MS, retryIntervalMs * 2);
}
} else {
synchronized (lock) {

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.plugin.Plugin;
interface MailboxConfig {
/**
* The minimum interval between API call retries in milliseconds.
*/
long getApiCallerMinRetryInterval();
/**
* The maximum interval between API call retries in milliseconds.
*/
long getApiCallerMaxRetryInterval();
/**
* How long (in milliseconds) the Tor plugin needs to be continuously
* {@link Plugin.State#ACTIVE active} before we assume our contacts can
* reach our hidden service.
*/
long getTorReachabilityPeriod();
}

View File

@@ -1,30 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxConfigImpl implements MailboxConfig {
@Inject
MailboxConfigImpl() {
}
@Override
public long getApiCallerMinRetryInterval() {
return MailboxApiCaller.MIN_RETRY_INTERVAL_MS;
}
@Override
public long getApiCallerMaxRetryInterval() {
return MailboxApiCaller.MAX_RETRY_INTERVAL_MS;
}
@Override
public long getTorReachabilityPeriod() {
return TorReachabilityMonitor.REACHABILITY_PERIOD_MS;
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
@@ -12,15 +11,12 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
@@ -30,7 +26,6 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
@@ -103,22 +98,6 @@ class MailboxManagerImpl implements MailboxManager {
return created;
}
@Override
public String convertBase32Payload(String base32Payload)
throws FormatException {
Pattern regex = Pattern.compile("(briar-mailbox://)?([a-z2-7]{104})");
Matcher matcher = regex.matcher(base32Payload);
if (!matcher.find()) throw new FormatException();
String base32 = matcher.group(2);
byte[] payloadBytes;
try {
payloadBytes = Base32.decode(base32, false);
} catch (IllegalArgumentException e) {
throw new FormatException();
}
return new String(payloadBytes, ISO_8859_1);
}
@Override
public boolean checkConnection() {
List<MailboxVersion> versions = null;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
@@ -75,11 +76,14 @@ public class MailboxModule {
ValidationManager validationManager,
ClientHelper clientHelper,
MetadataEncoder metadataEncoder,
Clock clock) {
Clock clock,
FeatureFlags featureFlags) {
MailboxUpdateValidator validator = new MailboxUpdateValidator(
clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
if (featureFlags.shouldEnableMailbox()) {
validationManager.registerMessageValidator(CLIENT_ID,
MAJOR_VERSION, validator);
}
return validator;
}
@@ -91,26 +95,31 @@ public class MailboxModule {
@Provides
@Singleton
MailboxUpdateManager provideMailboxUpdateManager(
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManagerImpl mailboxUpdateManager) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
}
return mailboxUpdateManager;
}
@Provides
@Singleton
MailboxFileManager provideMailboxFileManager(EventBus eventBus,
MailboxFileManagerImpl mailboxFileManager) {
eventBus.addListener(mailboxFileManager);
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags,
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
if (featureFlags.shouldEnableMailbox()) {
eventBus.addListener(mailboxFileManager);
}
return mailboxFileManager;
}
@@ -151,14 +160,17 @@ public class MailboxModule {
MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor,
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
EventBus eventBus) {
MailboxClientManager manager = new MailboxClientManager(eventExecutor,
dbExecutor, db, contactManager, pluginManager,
mailboxSettingsManager, mailboxUpdateManager,
mailboxClientFactory, reachabilityMonitor);
lifecycleManager.registerService(manager);
eventBus.addListener(manager);
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerService(manager);
eventBus.addListener(manager);
}
return manager;
}
}

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault;
@@ -26,7 +25,6 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
@Inject
MailboxPairingTaskFactoryImpl(
@@ -36,8 +34,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
MailboxUpdateManager mailboxUpdateManager) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
@@ -45,13 +42,12 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager, qrCodeClassifier);
mailboxUpdateManager);
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -10,26 +9,18 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -41,10 +32,7 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@ThreadSafe
@NotNullByDefault
@@ -52,6 +40,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload;
private final Executor eventExecutor;
@@ -61,8 +52,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
private final long timeStarted;
private final Object lock = new Object();
@GuardedBy("lock")
@@ -79,8 +68,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
MailboxUpdateManager mailboxUpdateManager) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
@@ -89,9 +77,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
timeStarted = clock.currentTimeMillis();
state = new QrCodeReceived(timeStarted);
state = new MailboxPairingState.QrCodeReceived();
}
@Override
@@ -113,30 +99,22 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
@Override
public void run() {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payload);
QrCodeType qrCodeType = typeAndVersion.getFirst();
int formatVersion = typeAndVersion.getSecond();
if (qrCodeType != MAILBOX || formatVersion != QR_FORMAT_VERSION) {
setState(new InvalidQrCode(qrCodeType, formatVersion));
return;
}
try {
pairMailbox();
} catch (FormatException e) {
onMailboxError(e, new InvalidQrCode(qrCodeType, formatVersion));
onMailboxError(e, new MailboxPairingState.InvalidQrCode());
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxAlreadyPaired());
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new ConnectionError());
onMailboxError(e, new MailboxPairingState.ConnectionError());
} catch (ApiException | DbException e) {
onMailboxError(e, new UnexpectedError());
onMailboxError(e, new MailboxPairingState.UnexpectedError());
}
}
private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new Pairing(timeStarted));
setState(new MailboxPairingState.Pairing());
MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis();
db.transaction(false, txn -> {
@@ -155,7 +133,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
}
}
});
setState(new Paired());
setState(new MailboxPairingState.Paired());
}
private void onMailboxError(Exception e, MailboxPairingState state) {
@@ -189,6 +167,14 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
}
throw new FormatException();
}
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey);

View File

@@ -32,7 +32,6 @@ class TorReachabilityMonitorImpl
private final Executor ioExecutor;
private final TaskScheduler taskScheduler;
private final MailboxConfig mailboxConfig;
private final PluginManager pluginManager;
private final EventBus eventBus;
private final Object lock = new Object();
@@ -51,12 +50,10 @@ class TorReachabilityMonitorImpl
TorReachabilityMonitorImpl(
@IoExecutor Executor ioExecutor,
TaskScheduler taskScheduler,
MailboxConfig mailboxConfig,
PluginManager pluginManager,
EventBus eventBus) {
this.ioExecutor = ioExecutor;
this.taskScheduler = taskScheduler;
this.mailboxConfig = mailboxConfig;
this.pluginManager = pluginManager;
this.eventBus = eventBus;
}
@@ -113,7 +110,7 @@ class TorReachabilityMonitorImpl
synchronized (lock) {
if (destroyed || task != null) return;
task = taskScheduler.schedule(this::onTorReachable, ioExecutor,
mailboxConfig.getTorReachabilityPeriod(), MILLISECONDS);
REACHABILITY_PERIOD_MS, MILLISECONDS);
}
}

View File

@@ -4,11 +4,7 @@ import dagger.Module;
import dagger.Provides;
@Module
public class ModularMailboxModule {
@Provides
MailboxConfig provideMailboxConfig(MailboxConfigImpl mailboxConfig) {
return mailboxConfig;
}
public class UrlConverterModule {
@Provides
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) {

View File

@@ -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,10 +288,8 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback {
private final TransportId id;
private final Object stateLock = new Object();
@GuardedBy("lock")
private State state = STARTING_STOPPING;
private final AtomicReference<State> state =
new AtomicReference<>(STARTING_STOPPING);
private Callback(TransportId id) {
this.id = id;
@@ -345,26 +343,22 @@ class PluginManagerImpl implements PluginManager, Service {
@Override
public void pluginStateChanged(State 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));
State oldState = state.getAndSet(newState);
if (newState != oldState) {
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));
}
}

View File

@@ -89,17 +89,6 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
private volatile String contactConnectionsUuid = null;
/**
* Override and return true, if the plugin is now allowed to access the
* Bluetooth hardware.
* If this returns false, the plugin must be
* {@link org.briarproject.bramble.api.plugin.Plugin.State#DISABLED}
* in {@link #start()} and not attempt to access Bluetooth hardware.
*/
protected boolean isBluetoothAccessible() {
return true;
}
abstract void initialiseAdapter() throws IOException;
abstract boolean isAdapterEnabled();
@@ -187,28 +176,19 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
DEFAULT_PREF_PLUGIN_ENABLE);
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
DEFAULT_PREF_EVER_CONNECTED));
// disable plugin, if conditions for enabling are not met
if (enabledByUser && !isBluetoothAccessible()) {
enabledByUser = false;
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
callback.mergeSettings(settings);
}
state.setStarted(enabledByUser);
try {
initialiseAdapter();
} catch (IOException e) {
throw new PluginException(e);
}
if (enabledByUser) {
updateProperties();
if (isAdapterEnabled()) bind();
}
updateProperties();
if (enabledByUser && isAdapterEnabled()) bind();
}
private void bind() {
ioExecutor.execute(() -> {
if (getState() != INACTIVE) return;
if (contactConnectionsUuid == null) updateProperties();
// Bind a server socket to accept connections from contacts
SS ss;
try {
@@ -554,8 +534,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE);
boolean shouldEnable = enabledByUser && isBluetoothAccessible();
SS ss = state.setEnabledByUser(shouldEnable);
SS ss = state.setEnabledByUser(enabledByUser);
State s = getState();
if (ss != null) {
LOG.info("Disabled by user, closing server socket");

View File

@@ -419,7 +419,7 @@ class LanTcpPlugin extends TcpPlugin {
private InetSocketAddress parseSocketAddress(BdfList descriptor)
throws FormatException {
byte[] address = descriptor.getRaw(1);
int port = descriptor.getInt(2);
int port = descriptor.getLong(2).intValue();
if (port < 1 || port > MAX_16_BIT_UNSIGNED) throw new FormatException();
try {
InetAddress addr = InetAddress.getByAddress(address);

View File

@@ -1,8 +1,5 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProviderFactory;
import javax.inject.Singleton;
import dagger.Module;
@@ -13,7 +10,8 @@ public class CircumventionModule {
@Provides
@Singleton
CircumventionProvider provideCircumventionProvider() {
return CircumventionProviderFactory.createCircumventionProvider();
CircumventionProvider provideCircumventionProvider(
CircumventionProviderImpl provider) {
return provider;
}
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.List;
@NotNullByDefault
public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
VANILLA,
MEEK,
SNOWFLAKE
}
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
* <p>
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}.
*/
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_BRIDGES = {"EG", "VE"};
/**
* Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
/**
* Countries where vanilla bridges are blocked via DPI but non-default
* obfs4 bridges, meek and snowflake may work. Should be a subset of
* {@link #BRIDGES}.
*/
String[] DPI_BRIDGES = {"CN", "IR", "TM"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode);
/**
* Returns the types of bridge connection that are suitable for the given
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
* to work.
*/
List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor
List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt);
}

View File

@@ -0,0 +1,155 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
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.CircumventionProviderImpl.SnowflakeBroker.AMP_CACHE;
import static org.briarproject.bramble.plugin.tor.CircumventionProviderImpl.SnowflakeBroker.AZURE;
import static org.briarproject.bramble.plugin.tor.CircumventionProviderImpl.SnowflakeBroker.FASTLY;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@Immutable
@NotNullByDefault
class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
private final static String DEFAULT_COUNTRY_CODE = "ZZ";
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES));
// Package access for testing
enum SnowflakeBroker {
FASTLY('F'),
AZURE('A'),
AMP_CACHE('M');
private final char value;
SnowflakeBroker(char value) {
this.value = value;
}
}
@Inject
CircumventionProviderImpl() {
}
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return BLOCKED_IN_COUNTRIES.contains(countryCode);
}
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGE_COUNTRIES.contains(countryCode);
}
@Override
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
} else {
return asList(DEFAULT_OBFS4, VANILLA);
}
}
@Override
@IoExecutor
public List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt) {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is);
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
(type == VANILLA && line.startsWith("v ")) ||
(type == MEEK && line.startsWith("m "))) {
bridges.add(line.substring(2));
} else if (type == SNOWFLAKE && line.startsWith("s ")) {
String params;
// If the client can verify Let's Encrypt certificates then
// use the Fastly broker, otherwise use Azure
if (letsEncrypt) {
params = getSnowflakeParams(countryCode, FASTLY);
} else {
params = getSnowflakeParams(countryCode, AZURE);
}
bridges.add(line.substring(2) + " " + params);
// Also use the AMP cache broker
params = getSnowflakeParams(countryCode, AMP_CACHE);
bridges.add(line.substring(2) + " " + params);
}
}
scanner.close();
return bridges;
}
// Package access for testing
String getSnowflakeParams(String countryCode, SnowflakeBroker broker) {
Map<String, String> params = loadSnowflakeParams();
if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
// If we have parameters for this country code, return them
String value = params.get(makeKey(countryCode, broker));
if (value != null) return value;
// Return the default parameters
value = params.get(makeKey(DEFAULT_COUNTRY_CODE, broker));
return requireNonNull(value);
}
private Map<String, String> loadSnowflakeParams() {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
Scanner scanner = new Scanner(is);
Map<String, String> params = new TreeMap<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.length() < 5) continue;
String key = line.substring(0, 4); // Country code, space, broker
String value = line.substring(5);
params.put(key, value);
}
scanner.close();
return params;
}
private String makeKey(String countryCode, SnowflakeBroker broker) {
return countryCode + " " + broker.value;
}
}

View File

@@ -1,5 +1,8 @@
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;
@@ -24,27 +27,36 @@ 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.nullsafety.InterfaceNotNullByDefault;
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.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProvider.BridgeType;
import org.briarproject.onionwrapper.LocationUtils;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.TorWrapper.HiddenServiceProperties;
import org.briarproject.onionwrapper.TorWrapper.Observer;
import org.briarproject.onionwrapper.TorWrapper.TorState;
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.nio.charset.Charset;
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;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
@@ -53,9 +65,13 @@ 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;
@@ -77,19 +93,34 @@ 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.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.isNullOrEmpty;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
@InterfaceNotNullByDefault
class TorPlugin implements DuplexPlugin, EventListener {
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, 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;
@@ -98,79 +129,91 @@ class TorPlugin implements DuplexPlugin, EventListener {
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final CircumventionProvider circumventionProvider;
private final Clock clock;
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, geoIpFile, 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,
boolean canVerifyLetsEncryptCerts) {
File torDirectory,
int torSocksPort,
int torControlPort) {
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;
this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
if (maxIdleTime > Integer.MAX_VALUE / 2) {
if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
} else {
socketTimeout = maxIdleTime * 2;
}
else socketTimeout = maxIdleTime * 2;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
geoIpFile = new File(torDirectory, "geoip");
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1);
tor.setObserver(new Observer() {
}
@Override
public void onState(TorState torState) {
State s = state.getState(torState);
if (s == ACTIVE) backoff.reset();
callback.pluginStateChanged(s);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor");
}
@Override
public void onBootstrapPercentage(int percentage) {
}
protected File getObfs4ExecutableFile() {
return new File(torDirectory, "obfs4proxy");
}
@Override
public void onHsDescriptorUpload(String onion) {
}
@Override
public void onClockSkewDetected(long skewSeconds) {
}
});
protected File getSnowflakeExecutableFile() {
return new File(torDirectory, "snowflake");
}
@Override
@@ -191,18 +234,89 @@ class TorPlugin implements DuplexPlugin, 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 {
tor.start();
// 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<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
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");
} 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());
@@ -210,6 +324,148 @@ class TorPlugin implements DuplexPlugin, 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();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.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(getTorInputStream(), 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(getObfs4InputStream(), 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(getSnowflakeInputStream(), snowflakeFile);
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
}
private InputStream getTorInputStream() throws IOException {
return getZipInputStream("tor");
}
private InputStream getObfs4InputStream() throws IOException {
return getZipInputStream("obfs4proxy");
}
private InputStream getSnowflakeInputStream() throws IOException {
return getZipInputStream("snowflake");
}
private InputStream getZipInputStream(String basename) throws IOException {
InputStream in = resourceProvider
.getResourceInputStream(basename + "_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
}
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);
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("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
@@ -233,9 +489,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
return;
}
// Store the port number
int localPort = ss.getLocalPort();
String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put(PREF_TOR_PORT, String.valueOf(localPort));
s.put(PREF_TOR_PORT, localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
@@ -245,28 +501,48 @@ class TorPlugin implements DuplexPlugin, EventListener {
});
}
private void publishHiddenService(int localPort) {
if (!tor.isTorRunning()) return;
String privKey = settings.get(HS_PRIVATE_KEY_V3);
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) {
LOG.info("Creating v3 hidden service");
HiddenServiceProperties hsProps;
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response;
try {
hsProps = tor.publishHiddenService(localPort, 80, privKey);
// 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);
}
} 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(hsProps.onion));
LOG.info("V3 hidden service " + scrubOnion(onion3));
}
if (privKey == null) {
// Publish the hidden service's onion hostname in transport props
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, hsProps.onion);
p.put(PROP_ONION_V3, onion3);
callback.mergeLocalProperties(p);
// Save the hidden service's private key for next time
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
callback.mergeSettings(s);
}
}
@@ -289,31 +565,50 @@ class TorPlugin implements DuplexPlugin, EventListener {
}
}
protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
if (bridgeTypes.isEmpty()) {
tor.disableBridges();
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
List<String> bridges = new ArrayList<>();
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
boolean letsEncrypt = canVerifyLetsEncryptCerts();
for (BridgeType bridgeType : bridgeTypes) {
bridges.addAll(circumventionProvider.getBridges(bridgeType,
countryCode, canVerifyLetsEncryptCerts));
conf.addAll(circumventionProvider
.getBridges(bridgeType, countryCode, letsEncrypt));
}
tor.enableBridges(bridges);
controlConnection.setConf(conf);
}
}
/**
* 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);
try {
tor.stop();
} catch (IOException e) {
logException(LOG, WARNING, e);
} catch (InterruptedException e) {
LOG.warning("Interrupted while stopping Tor");
Thread.currentThread().interrupt();
if (controlSocket != null && controlConnection != null) {
try {
LOG.info("Stopping Tor");
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
}
@@ -424,7 +719,6 @@ class TorPlugin implements DuplexPlugin, 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();
@@ -441,7 +735,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
LOG.info("Rendezvous server socket closed");
}
});
tor.publishHiddenService(port, 80, blob);
Map<Integer, String> portLines =
singletonMap(80, "127.0.0.1:" + port);
controlConnection.addOnion(blob, portLines);
return new RendezvousEndpoint() {
@Override
@@ -451,11 +747,8 @@ class TorPlugin implements DuplexPlugin, EventListener {
@Override
public void close() throws IOException {
try {
tor.removeHiddenService(localOnion);
} finally {
tryToClose(ss, LOG, WARNING);
}
controlConnection.delOnion(localOnion);
tryToClose(ss, LOG, WARNING);
}
};
} catch (IOException e) {
@@ -464,6 +757,120 @@ class TorPlugin implements DuplexPlugin, 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<String> 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()) {
throw new RuntimeException("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) {
@@ -486,7 +893,7 @@ class TorPlugin implements DuplexPlugin, EventListener {
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!tor.isTorRunning()) return;
if (!state.isTorRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
@@ -570,22 +977,41 @@ class TorPlugin implements DuplexPlugin, EventListener {
try {
if (enableNetwork) {
enableBridges(bridgeTypes, country);
tor.enableConnectionPadding(enableConnectionPadding);
tor.enableIpv6(ipv6Only);
enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only);
}
tor.enableNetwork(enableNetwork);
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
private class PluginState {
protected class PluginState {
@GuardedBy("this")
private boolean settingsChecked = false;
private boolean started = false,
stopped = false,
networkInitialised = false,
networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
@GuardedBy("this")
private int reasonsDisabled = 0;
@@ -594,13 +1020,84 @@ class TorPlugin implements DuplexPlugin, EventListener {
@Nullable
private ServerSocket serverSocket = null;
@GuardedBy("this")
private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> 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;
@@ -613,7 +1110,7 @@ class TorPlugin implements DuplexPlugin, EventListener {
// Doesn't affect getState()
private synchronized boolean setServerSocket(ServerSocket ss) {
if (serverSocket != null || !tor.isTorRunning()) return false;
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
@@ -623,31 +1120,57 @@ class TorPlugin implements DuplexPlugin, EventListener {
if (serverSocket == ss) serverSocket = null;
}
private synchronized State getState() {
return getState(tor.getTorState());
/**
* 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<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
}
private synchronized State getState(TorState torState) {
// Treat TorState.STARTED as State.STARTING_STOPPING because it's
// only seen during startup, before TorWrapper#enableNetwork() is
// called for the first time
if (torState == TorState.NOT_STARTED ||
torState == TorState.STARTING ||
torState == TorState.STARTED ||
torState == TorState.STOPPING ||
torState == TorState.STOPPED ||
!settingsChecked) {
private synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
if (torState == TorState.CONNECTING) return ENABLING;
if (torState == TorState.CONNECTED) return ACTIVE;
// The plugin is enabled in settings but the device is offline
return INACTIVE;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
? ACTIVE : ENABLING;
}
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");
}
}
}
}

View File

@@ -3,7 +3,6 @@ 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,10 +16,10 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.LocationUtils;
import java.io.File;
import java.util.concurrent.Executor;
@@ -46,12 +45,13 @@ 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, eventExecutor, wakefulIoExecutor;
protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
protected final Clock clock;
@@ -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;

View File

@@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
import java.nio.charset.Charset;
class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
@@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] hash = spec.getH();
byte[] base64 = Base64.encode(hash);
return "ED25519-V3:" + new String(base64, US_ASCII);
return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII"));
}
}

View File

@@ -201,7 +201,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId, false);
e.getValue().messageId);
local.put(e.getKey(), parseProperties(message));
}
return local;
@@ -222,7 +222,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest != null) {
// Retrieve and parse the latest local properties
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId, false);
latest.messageId);
p = parseProperties(message);
}
return p == null ? new TransportProperties() : p;
@@ -252,7 +252,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
local = new TransportProperties();
} else {
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId, false);
latest.messageId);
local = parseProperties(message);
}
storeLocalProperties(txn, c, t, local);
@@ -272,8 +272,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
remote = new TransportProperties();
} else {
// Retrieve and parse the latest remote properties
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId, false);
BdfList message =
clientHelper.getMessageAsList(txn, latest.messageId);
remote = parseProperties(message);
}
// Merge in any discovered properties
@@ -317,7 +317,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
changed = true;
} else {
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId, false);
latest.messageId);
TransportProperties old = parseProperties(message);
merged = new TransportProperties(old);
for (Entry<String, String> e : p.entrySet()) {

View File

@@ -27,10 +27,7 @@ class TransportPropertyValidator extends BdfMessageValidator {
TransportPropertyValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
// Accept transport properties in non-canonical form
// TODO: Remove this after a reasonable migration period
// (added 2023-02-17)
super(clientHelper, metadataEncoder, clock, false);
super(clientHelper, metadataEncoder, clock);
}
@Override

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class QrCodeClassifierImpl implements QrCodeClassifier {
@Inject
QrCodeClassifierImpl() {
}
@Override
public Pair<QrCodeType, Integer> classifyQrCode(String payload) {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length == 0) return new Pair<>(UNKNOWN, 0);
// If this is a Bramble QR code then the first byte encodes the
// format ID (3 bits) and version (5 bits)
int formatIdAndVersion = bytes[0] & 0xFF;
int formatId = formatIdAndVersion >> 5;
int formatVersion = formatIdAndVersion & 0x1F;
if (formatId == KeyAgreementConstants.QR_FORMAT_ID) {
return new Pair<>(BQP, formatVersion);
}
if (formatId == MailboxConstants.QR_FORMAT_ID) {
return new Pair<>(MAILBOX, formatVersion);
}
return new Pair<>(UNKNOWN, 0);
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import dagger.Module;
import dagger.Provides;
@Module
public class QrCodeModule {
@Provides
QrCodeClassifier provideQrCodeClassifier(
QrCodeClassifierImpl qrCodeClassifier) {
return qrCodeClassifier;
}
}

View File

@@ -30,7 +30,6 @@ import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.PRIORITY;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
@@ -127,8 +126,6 @@ class SyncRecordReaderImpl implements SyncRecordReader {
byte[] payload = nextRecord.getPayload();
if (payload.length <= MESSAGE_HEADER_LENGTH)
throw new FormatException();
if (payload.length > MAX_MESSAGE_LENGTH)
throw new FormatException();
// Validate timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException();

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -22,15 +21,18 @@ public class DefaultTaskSchedulerModule {
TaskScheduler scheduler;
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager,
ThreadFactory threadFactory) {
private final ScheduledExecutorService scheduledExecutorService;
public DefaultTaskSchedulerModule() {
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
ScheduledExecutorService scheduledExecutorService =
new ScheduledThreadPoolExecutor(1, threadFactory, policy);
scheduledExecutorService = new ScheduledThreadPoolExecutor(1, policy);
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(scheduledExecutorService);
return new TaskSchedulerImpl(scheduledExecutorService);
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.system;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class DefaultThreadFactoryModule {
@Provides
@Singleton
ThreadFactory provideThreadFactory() {
return Executors.defaultThreadFactory();
}
}

View File

@@ -32,7 +32,8 @@ class SessionParserImpl implements SessionParser {
@Override
public Session parseSession(BdfDictionary meta) throws FormatException {
State state = State.fromValue(meta.getInt(SESSION_KEY_STATE));
State state =
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes =
@@ -55,9 +56,9 @@ class SessionParserImpl implements SessionParser {
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null;
Integer keySetIdInt = meta.getOptionalInt(SESSION_KEY_KEY_SET_ID);
if (keySetIdInt != null) {
keySetId = new KeySetId(keySetIdInt);
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) {
keySetId = new KeySetId(keySetIdLong.intValue());
}
return new Session(state, lastLocalMessageId, localKeyPair,

View File

@@ -177,8 +177,8 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfList body, BdfDictionary meta)
throws DbException, FormatException {
MessageType type =
MessageType.fromValue(meta.getInt(MSG_KEY_MESSAGE_TYPE));
MessageType type = MessageType.fromValue(
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Received " + type + " message for " + t);

View File

@@ -42,7 +42,7 @@ class TransportKeyAgreementValidator extends BdfMessageValidator {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getInt(0));
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
if (type == KEY) return validateKeyMessage(m.getTimestamp(), body);
else if (type == ACTIVATE) return validateActivateMessage(body);
else throw new AssertionError();

View File

@@ -228,11 +228,15 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
}
// Broadcast events for any client version update
// Broadcast events for any new client versions
Set<ClientVersion> oldRemoteVersions = new HashSet<>();
for (ClientState cs : oldRemoteStates) {
oldRemoteVersions.add(cs.clientVersion);
}
for (ClientState cs : newRemoteStates) {
if (!oldRemoteStates.contains(cs)) {
txn.attach(
new ClientVersionUpdatedEvent(c, cs.clientVersion));
if (!oldRemoteVersions.contains(cs.clientVersion)) {
txn.attach(new ClientVersionUpdatedEvent(c,
cs.clientVersion));
}
}
} catch (FormatException e) {
@@ -301,8 +305,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
for (int i = 0; i < size; i++) {
BdfList cv = body.getList(i);
ClientId clientId = new ClientId(cv.getString(0));
int majorVersion = cv.getInt(1);
int minorVersion = cv.getInt(2);
int majorVersion = cv.getLong(1).intValue();
int minorVersion = cv.getLong(2).intValue();
parsed.add(new ClientVersion(clientId, majorVersion, minorVersion));
}
return parsed;
@@ -408,8 +412,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
throws FormatException {
// Client ID, major version, minor version, active
ClientId clientId = new ClientId(clientState.getString(0));
int majorVersion = clientState.getInt(1);
int minorVersion = clientState.getInt(2);
int majorVersion = clientState.getLong(1).intValue();
int minorVersion = clientState.getLong(2).intValue();
boolean active = clientState.getBoolean(3);
return new ClientState(clientId, majorVersion, minorVersion, active);
}

Some files were not shown because too many files have changed in this diff Show More