mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Compare commits
159 Commits
release-1.
...
alpha-1.5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a8c6256a5 | ||
|
|
c076a8e2cd | ||
|
|
c0234ebe13 | ||
|
|
b15d316946 | ||
|
|
e40c0caf97 | ||
|
|
f3af1d90d1 | ||
|
|
fe6ce2fe67 | ||
|
|
65f7a02c33 | ||
|
|
ea7433ffca | ||
|
|
48b6dcf0ff | ||
|
|
ca9181ee3f | ||
|
|
f16875c602 | ||
|
|
551dba9425 | ||
|
|
07f49e4f1e | ||
|
|
ca14b4bd68 | ||
|
|
58883467f7 | ||
|
|
422a99888b | ||
|
|
28453f28d0 | ||
|
|
f00cfe5ca9 | ||
|
|
3ecb281695 | ||
|
|
eb3a5423bf | ||
|
|
73fa1052cf | ||
|
|
14fded3777 | ||
|
|
17f5433ab0 | ||
|
|
1752bca2ae | ||
|
|
9d9a7ff99d | ||
|
|
36e69c54df | ||
|
|
a3b10cc0d1 | ||
|
|
f683d4f3a9 | ||
|
|
ea1c58110f | ||
|
|
b559c7782d | ||
|
|
6ae601e395 | ||
|
|
c5c1fdb61c | ||
|
|
cc9ebe9eda | ||
|
|
05b9dd699e | ||
|
|
09a9a00af6 | ||
|
|
67797d0378 | ||
|
|
87ef5e58ee | ||
|
|
b8b5e6c201 | ||
|
|
4b11f3c0b3 | ||
|
|
cc47c8522a | ||
|
|
b68d24dca5 | ||
|
|
8bb3ea8a85 | ||
|
|
e13563952b | ||
|
|
c74ebabcd1 | ||
|
|
47b8f47f07 | ||
|
|
d0feacd38f | ||
|
|
2844adb8fa | ||
|
|
f02dcc9f70 | ||
|
|
8ab7eb7edf | ||
|
|
1ef1ccc1f7 | ||
|
|
c7e382c1af | ||
|
|
38a7217c3f | ||
|
|
6d3e81a074 | ||
|
|
4591de2017 | ||
|
|
6da34fac84 | ||
|
|
810ac24cee | ||
|
|
704f69c9fd | ||
|
|
952ee42ad1 | ||
|
|
f61b09d5a9 | ||
|
|
8f735d176e | ||
|
|
c47253fc5f | ||
|
|
b1cc63cd49 | ||
|
|
8cd6546840 | ||
|
|
7a0fb74c09 | ||
|
|
882f536b8d | ||
|
|
74f8e84a9b | ||
|
|
23df2a41c2 | ||
|
|
c77eaf16d9 | ||
|
|
9a6bb4b203 | ||
|
|
3d237a9104 | ||
|
|
fa216ffc6f | ||
|
|
a34631d36c | ||
|
|
45cda191e5 | ||
|
|
2495b6f5c0 | ||
|
|
03fc504f7d | ||
|
|
d19062e319 | ||
|
|
fdb429ab7a | ||
|
|
d0c59a6d75 | ||
|
|
3bb39c2aa3 | ||
|
|
917fc5e5b6 | ||
|
|
caa078585b | ||
|
|
e68c0c7f4b | ||
|
|
a6b3749fb6 | ||
|
|
a8f6e8e4bd | ||
|
|
4d884601f0 | ||
|
|
b71198d9b1 | ||
|
|
079c6e0475 | ||
|
|
3a0f8ed85c | ||
|
|
57f7501780 | ||
|
|
3cc5699fe0 | ||
|
|
7d761710e6 | ||
|
|
7461d3c943 | ||
|
|
9291613175 | ||
|
|
ce6739a9fd | ||
|
|
1f1a97f62d | ||
|
|
7a33d26533 | ||
|
|
f2c85f37be | ||
|
|
8e3fa872fd | ||
|
|
0d1e81ebdb | ||
|
|
bded4e7bc8 | ||
|
|
bf1a5cf218 | ||
|
|
dd7a638984 | ||
|
|
942222131e | ||
|
|
643757e407 | ||
|
|
7c530ad7a3 | ||
|
|
23b2dfa4a8 | ||
|
|
ce10e6770f | ||
|
|
b88dbee881 | ||
|
|
0ca21ad4c0 | ||
|
|
a14f62dcc3 | ||
|
|
f0c1ebcc1b | ||
|
|
4a4b04bec3 | ||
|
|
6f57ec8281 | ||
|
|
0eb0bbdc99 | ||
|
|
76344344d2 | ||
|
|
624f11a61f | ||
|
|
fbc32830bd | ||
|
|
145117a1dc | ||
|
|
6ed55bcd7d | ||
|
|
c6a284bd6d | ||
|
|
9d4d992009 | ||
|
|
e92eb1c699 | ||
|
|
07e56f7086 | ||
|
|
fe31e60e66 | ||
|
|
7810e7e848 | ||
|
|
f8015272f4 | ||
|
|
2566105f13 | ||
|
|
cab8f834bd | ||
|
|
ec0a754289 | ||
|
|
e81fe44ea1 | ||
|
|
e399b9196a | ||
|
|
aadbd3a662 | ||
|
|
f4fd65aee4 | ||
|
|
61e7d2ebf9 | ||
|
|
06dd8c65aa | ||
|
|
2f351b318e | ||
|
|
a468af94db | ||
|
|
49f10e7e82 | ||
|
|
01b1741e83 | ||
|
|
b7003a3587 | ||
|
|
3dbf327937 | ||
|
|
462f57c966 | ||
|
|
8d20c5d8b8 | ||
|
|
73d806f8b9 | ||
|
|
f1ae57b213 | ||
|
|
cae9efb4bf | ||
|
|
39ac737015 | ||
|
|
edd3310d03 | ||
|
|
a09d88daa8 | ||
|
|
3dc984659d | ||
|
|
f580525734 | ||
|
|
fbf0f63ff7 | ||
|
|
ee9234e12e | ||
|
|
2657e2bc08 | ||
|
|
3c40c11dfb | ||
|
|
3bdbabf38a | ||
|
|
a378c24af8 | ||
|
|
b09ea495e7 |
@@ -88,25 +88,9 @@ test_reproducible:
|
||||
only:
|
||||
- tags
|
||||
|
||||
.optional_tests:
|
||||
mailbox integration test:
|
||||
stage: optional_tests
|
||||
extends: .base-test
|
||||
|
||||
bridge test:
|
||||
extends: .optional_tests
|
||||
rules:
|
||||
- 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:
|
||||
- changes:
|
||||
- mailbox-integration-tests/**/*
|
||||
|
||||
6
.idea/codeStyles/Project.xml
generated
6
.idea/codeStyles/Project.xml
generated
@@ -28,15 +28,11 @@
|
||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="Groovy">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
</indentOptions>
|
||||
|
||||
28
.idea/runConfigurations/BridgeTest.xml
generated
28
.idea/runConfigurations/BridgeTest.xml
generated
@@ -1,28 +0,0 @@
|
||||
<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 "org.briarproject.bramble.plugin.tor.BridgeTest"" />
|
||||
<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>
|
||||
10
README.md
10
README.md
@@ -1,7 +1,7 @@
|
||||
# Briar
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
|
||||
|
||||
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
|
||||
Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
|
||||
|
||||
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
@@ -14,14 +14,16 @@ You can also [download the APK file](https://briarproject.org/apk) directly from
|
||||
our site.
|
||||
|
||||
## Useful links
|
||||
[briarproject.org](https://briarproject.org/)
|
||||
[Project website](https://briarproject.org/)
|
||||
|
||||
[Source code](https://code.briarproject.org/briar/briar/tree/master)
|
||||
|
||||
[Manual](https://briarproject.org/manual/)
|
||||
[User manual](https://briarproject.org/manual/)
|
||||
|
||||
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
|
||||
|
||||
[Privacy policy](https://briarproject.org/privacy)
|
||||
|
||||
## Reproducible builds
|
||||
|
||||
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
|
||||
@@ -33,5 +35,5 @@ for reproduction.
|
||||
|
||||
## Donate
|
||||
|
||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||
[](https://liberapay.com/Briar/donate)
|
||||
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K
|
||||
|
||||
@@ -11,10 +11,10 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
versionCode 10423
|
||||
versionName "1.4.23"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 10511
|
||||
versionName "1.5.11"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -40,6 +40,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
|
||||
@@ -49,6 +51,7 @@ dependencies {
|
||||
implementation project(':bramble-core')
|
||||
|
||||
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"
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
|
||||
<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 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="31" />
|
||||
<!-- 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/33 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
@@ -8,6 +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;
|
||||
|
||||
@@ -19,6 +20,7 @@ import dagger.Module;
|
||||
AndroidSystemModule.class,
|
||||
AndroidTaskSchedulerModule.class,
|
||||
AndroidWakefulIoExecutorModule.class,
|
||||
AndroidWakeLockModule.class,
|
||||
DefaultThreadFactoryModule.class,
|
||||
CircumventionModule.class,
|
||||
DnsModule.class,
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
@@ -105,15 +104,11 @@ class AndroidAccountManager extends AccountManagerImpl
|
||||
}
|
||||
files.add(appContext.getFilesDir());
|
||||
addIfNotNull(files, appContext.getExternalCacheDir());
|
||||
if (SDK_INT >= 19) {
|
||||
for (File file : appContext.getExternalCacheDirs()) {
|
||||
addIfNotNull(files, file);
|
||||
}
|
||||
for (File file : appContext.getExternalCacheDirs()) {
|
||||
addIfNotNull(files, file);
|
||||
}
|
||||
if (SDK_INT >= 21) {
|
||||
for (File file : appContext.getExternalMediaDirs()) {
|
||||
addIfNotNull(files, file);
|
||||
}
|
||||
for (File file : appContext.getExternalMediaDirs()) {
|
||||
addIfNotNull(files, file);
|
||||
}
|
||||
// Clear the cache directory but don't delete it
|
||||
File cacheDir = appContext.getCacheDir();
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface AndroidWakeLock {
|
||||
|
||||
/**
|
||||
* Acquires the wake lock. This has no effect if the wake lock has already
|
||||
* been acquired.
|
||||
*/
|
||||
void acquire();
|
||||
|
||||
/**
|
||||
* Releases the wake lock. This has no effect if the wake lock has already
|
||||
* been released.
|
||||
*/
|
||||
void release();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface AndroidWakeLockManager {
|
||||
|
||||
/**
|
||||
* Creates a wake lock with the given tag. The tag is only used for
|
||||
* logging; the underlying OS wake lock will use its own tag.
|
||||
*/
|
||||
AndroidWakeLock createWakeLock(String tag);
|
||||
|
||||
/**
|
||||
* Runs the given task while holding a wake lock.
|
||||
*/
|
||||
void runWakefully(Runnable r, String tag);
|
||||
|
||||
/**
|
||||
* Submits the given task to the given executor while holding a wake lock.
|
||||
* The lock is released when the task completes, or if an exception is
|
||||
* thrown while submitting or running the task.
|
||||
*/
|
||||
void executeWakefully(Runnable r, Executor executor, String tag);
|
||||
|
||||
/**
|
||||
* Starts a dedicated thread to run the given task asynchronously. A wake
|
||||
* lock is acquired before starting the thread and released when the task
|
||||
* completes, or if an exception is thrown while starting the thread or
|
||||
* running the task.
|
||||
* <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);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.battery.event.BatteryEvent;
|
||||
@@ -16,10 +17,17 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import static android.content.Intent.ACTION_BATTERY_CHANGED;
|
||||
import static android.content.Intent.ACTION_POWER_CONNECTED;
|
||||
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
|
||||
import static android.os.BatteryManager.EXTRA_PLUGGED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
|
||||
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_POWER_CONNECTED);
|
||||
filter.addAction(ACTION_POWER_DISCONNECTED);
|
||||
filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED);
|
||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
if (SDK_INT >= 33) {
|
||||
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
|
||||
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
|
||||
}
|
||||
appContext.registerReceiver(batteryReceiver, filter);
|
||||
}
|
||||
|
||||
@@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service {
|
||||
eventBus.broadcast(new BatteryEvent(true));
|
||||
else if (ACTION_POWER_DISCONNECTED.equals(action))
|
||||
eventBus.broadcast(new BatteryEvent(false));
|
||||
else if (SDK_INT >= 23 &&
|
||||
ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) &&
|
||||
LOG.isLoggable(INFO)) {
|
||||
LOG.info("Device idle mode changed to: " +
|
||||
getPowerManager(ctx).isDeviceIdleMode());
|
||||
} else if (SDK_INT >= 23 &&
|
||||
ACTION_POWER_SAVE_MODE_CHANGED.equals(action) &&
|
||||
LOG.isLoggable(INFO)) {
|
||||
LOG.info("Power save mode changed to: " +
|
||||
getPowerManager(ctx).isPowerSaveMode());
|
||||
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
|
||||
ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
|
||||
PowerManager powerManager =
|
||||
ctx.getSystemService(PowerManager.class);
|
||||
LOG.info("Low power standby now is: " +
|
||||
powerManager.isLowPowerStandbyEnabled());
|
||||
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
|
||||
ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
|
||||
PowerManager powerManager = getPowerManager(ctx);
|
||||
LOG.info("Light idle mode now is: " +
|
||||
powerManager.isDeviceLightIdleMode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = 23)
|
||||
private PowerManager getPowerManager(Context ctx) {
|
||||
return ctx.getSystemService(PowerManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
@@ -49,7 +50,6 @@ 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;
|
||||
@@ -60,6 +60,7 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@SuppressLint("MissingPermission")
|
||||
class AndroidBluetoothPlugin extends
|
||||
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
|
||||
|
||||
@@ -253,7 +254,7 @@ class AndroidBluetoothPlugin extends
|
||||
} else if (ACTION_FOUND.equals(action)) {
|
||||
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
|
||||
// Ignore Bluetooth LE devices
|
||||
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
|
||||
if (d.getType() != DEVICE_TYPE_LE) {
|
||||
String address = d.getAddress();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Discovered " +
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
@@ -13,7 +14,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
@@ -37,7 +36,6 @@ 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;
|
||||
@@ -118,7 +116,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 SDK_INT >= 21 ? getWifiClientIpv6Address() : null;
|
||||
return getWifiClientIpv6Address();
|
||||
}
|
||||
// Use the wifi IPv4 address to determine which interface's IPv6
|
||||
// address we should return (if the interface has a suitable address)
|
||||
@@ -172,7 +170,6 @@ 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
|
||||
@@ -204,6 +201,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
@Nullable
|
||||
private InetAddress getIpv6AddressForInterface(InetAddress ipv4) {
|
||||
try {
|
||||
// We may get an NPE from getByInetAddress() on Android 11
|
||||
NetworkInterface iface = NetworkInterface.getByInetAddress(ipv4);
|
||||
if (iface == null) return null;
|
||||
for (InetAddress addr : list(iface.getInetAddresses())) {
|
||||
@@ -211,7 +209,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
}
|
||||
// No suitable address
|
||||
return null;
|
||||
} catch (SocketException e) {
|
||||
} catch (SocketException | NullPointerException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
@@ -234,7 +232,6 @@ 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()) {
|
||||
@@ -302,7 +299,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 && SDK_INT >= 21) {
|
||||
if (wifi == null) {
|
||||
InetAddress ipv6 = getWifiClientIpv6Address();
|
||||
if (ipv6 != null) return new Pair<>(ipv6, false);
|
||||
}
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -13,12 +15,13 @@ 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;
|
||||
@@ -39,13 +42,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
@@ -55,8 +58,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
@TorControlPort int torControlPort,
|
||||
Application app,
|
||||
AndroidWakeLockManager wakeLockManager) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
this.app = app;
|
||||
@@ -79,12 +82,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new AndroidTorPlugin(ioExecutor,
|
||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, wakeLockManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
|
||||
ioExecutor, eventExecutor, architecture, torDirectory,
|
||||
torSocksPort, torControlPort);
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, tor, callback, MAX_LATENCY,
|
||||
MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,6 @@ import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
@NotNullByDefault
|
||||
class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
|
||||
|
||||
private static final int SEED_LENGTH = 32;
|
||||
|
||||
private final Context appContext;
|
||||
|
||||
@Inject
|
||||
@@ -72,27 +70,6 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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;
|
||||
@@ -46,8 +48,9 @@ public class AndroidSystemModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
LocationUtils provideLocationUtils(AndroidLocationUtils locationUtils) {
|
||||
return locationUtils;
|
||||
@Singleton
|
||||
LocationUtils provideLocationUtils(Application app) {
|
||||
return AndroidLocationUtilsFactory.createAndroidLocationUtils(app);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -69,11 +72,4 @@ public class AndroidSystemModule {
|
||||
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
AndroidWakeLockManager provideWakeLockManager(
|
||||
AndroidWakeLockManagerImpl wakeLockManager) {
|
||||
return wakeLockManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLock;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
/**
|
||||
* A wrapper around a {@link SharedWakeLock} that provides the more convenient
|
||||
* semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release()
|
||||
* don't need to be balanced).
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class AndroidWakeLockImpl implements AndroidWakeLock {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidWakeLockImpl.class.getName());
|
||||
|
||||
private static final AtomicInteger INSTANCE_ID = new AtomicInteger(0);
|
||||
|
||||
private final SharedWakeLock sharedWakeLock;
|
||||
private final String tag;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private boolean held = false;
|
||||
|
||||
AndroidWakeLockImpl(SharedWakeLock sharedWakeLock, String tag) {
|
||||
this.sharedWakeLock = sharedWakeLock;
|
||||
this.tag = tag + "_" + INSTANCE_ID.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acquire() {
|
||||
synchronized (lock) {
|
||||
if (held) {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine(tag + " already acquired");
|
||||
}
|
||||
} else {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine(tag + " acquiring shared wake lock");
|
||||
}
|
||||
held = true;
|
||||
sharedWakeLock.acquire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
synchronized (lock) {
|
||||
if (held) {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine(tag + " releasing shared wake lock");
|
||||
}
|
||||
held = false;
|
||||
sharedWakeLock.release();
|
||||
} else {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine(tag + " already released");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLock;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
|
||||
|
||||
/**
|
||||
* How often to replace the wake lock.
|
||||
*/
|
||||
private static final long LOCK_DURATION_MS = MINUTES.toMillis(1);
|
||||
|
||||
/**
|
||||
* Automatically release the lock this many milliseconds after it's due
|
||||
* to have been replaced and released.
|
||||
*/
|
||||
private static final long SAFETY_MARGIN_MS = SECONDS.toMillis(30);
|
||||
|
||||
private final SharedWakeLock sharedWakeLock;
|
||||
|
||||
@Inject
|
||||
AndroidWakeLockManagerImpl(Application app,
|
||||
ScheduledExecutorService scheduledExecutorService) {
|
||||
PowerManager powerManager = (PowerManager)
|
||||
requireNonNull(app.getSystemService(POWER_SERVICE));
|
||||
String tag = getWakeLockTag(app);
|
||||
sharedWakeLock = new RenewableWakeLock(powerManager,
|
||||
scheduledExecutorService, PARTIAL_WAKE_LOCK, tag,
|
||||
LOCK_DURATION_MS, SAFETY_MARGIN_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidWakeLock createWakeLock(String tag) {
|
||||
return new AndroidWakeLockImpl(sharedWakeLock, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runWakefully(Runnable r, String tag) {
|
||||
AndroidWakeLock wakeLock = createWakeLock(tag);
|
||||
wakeLock.acquire();
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeWakefully(Runnable r, Executor executor, String tag) {
|
||||
AndroidWakeLock wakeLock = createWakeLock(tag);
|
||||
wakeLock.acquire();
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
// Release the wake lock if the task throws an exception
|
||||
wakeLock.release();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// Release the wake lock if the executor throws an exception when
|
||||
// we submit the task (in which case the release() call above won't
|
||||
// happen)
|
||||
wakeLock.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeWakefully(Runnable r, String tag) {
|
||||
AndroidWakeLock wakeLock = createWakeLock(tag);
|
||||
wakeLock.acquire();
|
||||
try {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
wakeLock.release();
|
||||
}
|
||||
}).start();
|
||||
} catch (Exception e) {
|
||||
wakeLock.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String getWakeLockTag(Context ctx) {
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
if (isInstalled(pm, "com.huawei.powergenie")) {
|
||||
return "LocationManagerService";
|
||||
} else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
|
||||
return "AudioIn";
|
||||
}
|
||||
return ctx.getPackageName();
|
||||
}
|
||||
|
||||
private boolean isInstalled(PackageManager pm, String packageName) {
|
||||
try {
|
||||
pm.getPackageInfo(packageName, 0);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class RenewableWakeLock implements SharedWakeLock {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(RenewableWakeLock.class.getName());
|
||||
|
||||
private final PowerManager powerManager;
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
private final int levelAndFlags;
|
||||
private final String tag;
|
||||
private final long durationMs, safetyMarginMs;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private WakeLock wakeLock;
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Future<?> future;
|
||||
@GuardedBy("lock")
|
||||
private int refCount = 0;
|
||||
@GuardedBy("lock")
|
||||
private long acquired = 0;
|
||||
|
||||
RenewableWakeLock(PowerManager powerManager,
|
||||
ScheduledExecutorService scheduledExecutorService,
|
||||
int levelAndFlags,
|
||||
String tag,
|
||||
long durationMs,
|
||||
long safetyMarginMs) {
|
||||
this.powerManager = powerManager;
|
||||
this.scheduledExecutorService = scheduledExecutorService;
|
||||
this.levelAndFlags = levelAndFlags;
|
||||
this.tag = tag;
|
||||
this.durationMs = durationMs;
|
||||
this.safetyMarginMs = safetyMarginMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acquire() {
|
||||
synchronized (lock) {
|
||||
refCount++;
|
||||
if (refCount == 1) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Acquiring wake lock " + tag);
|
||||
}
|
||||
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
|
||||
// We do our own reference counting so we can replace the lock
|
||||
// TODO: Check whether using a ref-counted wake lock affects
|
||||
// power management apps
|
||||
wakeLock.setReferenceCounted(false);
|
||||
wakeLock.acquire(durationMs + safetyMarginMs);
|
||||
future = scheduledExecutorService.schedule(this::renew,
|
||||
durationMs, MILLISECONDS);
|
||||
acquired = android.os.SystemClock.elapsedRealtime();
|
||||
} else if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renew() {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag);
|
||||
synchronized (lock) {
|
||||
if (wakeLock == null) {
|
||||
LOG.info("Already released");
|
||||
return;
|
||||
}
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
|
||||
}
|
||||
long now = android.os.SystemClock.elapsedRealtime();
|
||||
long expiry = acquired + durationMs + safetyMarginMs;
|
||||
if (now > expiry && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Wake lock expired " + (now - expiry) + " ms ago");
|
||||
}
|
||||
WakeLock oldWakeLock = wakeLock;
|
||||
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
|
||||
wakeLock.setReferenceCounted(false);
|
||||
wakeLock.acquire(durationMs + safetyMarginMs);
|
||||
oldWakeLock.release();
|
||||
future = scheduledExecutorService.schedule(this::renew, durationMs,
|
||||
MILLISECONDS);
|
||||
acquired = now;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
synchronized (lock) {
|
||||
refCount--;
|
||||
if (refCount == 0) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Releasing wake lock " + tag);
|
||||
}
|
||||
requireNonNull(future).cancel(false);
|
||||
future = null;
|
||||
requireNonNull(wakeLock).release();
|
||||
wakeLock = null;
|
||||
acquired = 0;
|
||||
} else if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLock;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface SharedWakeLock {
|
||||
|
||||
/**
|
||||
* Acquires the wake lock. This increments the wake lock's reference count,
|
||||
* so unlike {@link AndroidWakeLock#acquire()} every call to this method
|
||||
* must be followed by a balancing call to {@link #release()}.
|
||||
*/
|
||||
void acquire();
|
||||
|
||||
/**
|
||||
* Releases the wake lock. This decrements the wake lock's reference count,
|
||||
* so unlike {@link AndroidWakeLock#release()} every call to this method
|
||||
* must follow a balancing call to {@link #acquire()}.
|
||||
*/
|
||||
void release();
|
||||
}
|
||||
@@ -11,14 +11,10 @@ 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;
|
||||
|
||||
@@ -29,7 +25,6 @@ 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;
|
||||
|
||||
@@ -43,14 +38,7 @@ public class AndroidUtils {
|
||||
private static final String STORED_LOGCAT = "dev-logcat";
|
||||
|
||||
public static Collection<String> getSupportedArchitectures() {
|
||||
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;
|
||||
return asList(Build.SUPPORTED_ABIS);
|
||||
}
|
||||
|
||||
public static boolean hasBtConnectPermission(Context ctx) {
|
||||
@@ -75,10 +63,17 @@ public class AndroidUtils {
|
||||
return new Pair<>(address, "adapter");
|
||||
}
|
||||
// Return the address from settings if it's valid and not fake
|
||||
address = Settings.Secure.getString(ctx.getContentResolver(),
|
||||
"bluetooth_address");
|
||||
if (isValidBluetoothAddress(address)) {
|
||||
return new Pair<>(address, "settings");
|
||||
if (SDK_INT < 33) {
|
||||
try {
|
||||
address = Settings.Secure.getString(ctx.getContentResolver(),
|
||||
"bluetooth_address");
|
||||
if (isValidBluetoothAddress(address)) {
|
||||
return new Pair<>(address, "settings");
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// Some custom ROMs throw this exception on SDK_INT < 33.
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
// Try to get the address via reflection
|
||||
address = getBluetoothAddressByReflection(adapter);
|
||||
@@ -136,19 +131,6 @@ 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();
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ 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();
|
||||
@@ -109,6 +113,12 @@ 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());
|
||||
@@ -125,6 +135,10 @@ 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();
|
||||
|
||||
@@ -142,6 +156,10 @@ 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
|
||||
|
||||
@@ -1,59 +1,67 @@
|
||||
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.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.43.2:dagger-compiler-2.43.2.jar:298c020ee6ed2f4cc651ebbfdb7f8de329b07c44b618d65be117846a850e2a03',
|
||||
'com.google.dagger:dagger-producers:2.43.2:dagger-producers-2.43.2.jar:e7f5d9ffc85d48a49c8e22e02833d418f7ccad5d7512f529964db5127ab915ff',
|
||||
'com.google.dagger:dagger-spi:2.43.2:dagger-spi-2.43.2.jar:3bae8d9dadeaaa5927da6f094389a560c12c05fec3d2711d2fa79292c7a7d7ad',
|
||||
'com.google.dagger:dagger:2.43.2:dagger-2.43.2.jar:c89681f7cbbf8c527bf4ac2748515d617fdb54a1d425c08d914fdc28192b5fe4',
|
||||
'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.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: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.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'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',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
|
||||
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
|
||||
'org.briarproject:tor-android:0.4.7.13-2:tor-android-0.4.7.13-2.jar:453fd463b234a2104edd7f0d02d0649cbb5c5efbe47a76df3828f55a3f90f8b5',
|
||||
'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.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-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
|
||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||
'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.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.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
]
|
||||
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.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.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: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.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'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',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.briarproject:dont-kill-me-lib:0.2.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.1.0:onionwrapper-android-0.1.0.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
|
||||
'org.briarproject:onionwrapper-core:0.1.0:onionwrapper-core-0.1.0.jar:3e6631771b891c959403f6145de034c6f9816e7d067808d534f954eef9a1ca35',
|
||||
'org.briarproject:snowflake-android:2.9.1:snowflake-android-2.9.1.jar:8d6195637edbe3717d205c2e524e9d3cb742b90fc67b10565d16262af134e489',
|
||||
'org.briarproject:tor-android:0.4.8.9-1:tor-android-0.4.8.9-1.jar:8fbaaf0cb1663abd12852b7fc51a804534b7e7d865b4dec3fc4e9ec0e79f3ad5',
|
||||
'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.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.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.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.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableMailbox();
|
||||
|
||||
boolean shouldEnablePrivateGroupsInCore();
|
||||
|
||||
boolean shouldEnableForumsInCore();
|
||||
|
||||
@@ -31,6 +31,12 @@ public abstract class BdfMessageValidator implements MessageValidator {
|
||||
protected final Clock clock;
|
||||
protected final boolean canonical;
|
||||
|
||||
/**
|
||||
* Transitional alternative to
|
||||
* {@link #BdfMessageValidator(ClientHelper, MetadataEncoder, Clock)} that
|
||||
* accepts messages in non-canonical form, for backward compatibility.
|
||||
*/
|
||||
@Deprecated
|
||||
protected BdfMessageValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock, boolean canonical) {
|
||||
this.clientHelper = clientHelper;
|
||||
|
||||
@@ -49,6 +49,15 @@ public interface ClientHelper {
|
||||
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
|
||||
FormatException;
|
||||
|
||||
/**
|
||||
* Transitional alternative to
|
||||
* {@link #getMessageAsList(Transaction, MessageId)} that allows the
|
||||
* message to be in non-canonical form, for backward compatibility.
|
||||
*
|
||||
* @param canonical True if the message must be in canonical form (a
|
||||
* {@link FormatException} will be thrown if it's not.
|
||||
*/
|
||||
@Deprecated
|
||||
BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical)
|
||||
throws DbException, FormatException;
|
||||
|
||||
@@ -109,6 +118,14 @@ public interface ClientHelper {
|
||||
|
||||
BdfList toList(Message m) throws FormatException;
|
||||
|
||||
/**
|
||||
* Transitional alternative to {@link #toList(Message)} that allows the
|
||||
* message to be in non-canonical form, for backward compatibility.
|
||||
*
|
||||
* @param canonical True if the message must be in canonical form (a
|
||||
* {@link FormatException} will be thrown if it's not.
|
||||
*/
|
||||
@Deprecated
|
||||
BdfList toList(Message m, boolean canonical) throws FormatException;
|
||||
|
||||
BdfList toList(Author a);
|
||||
|
||||
@@ -54,6 +54,38 @@ public interface CryptoComponent {
|
||||
KeyPair ourKeyPair, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
* <p>
|
||||
* Do not use this method for new protocols. The shared secret can be
|
||||
* re-derived using the ephemeral public keys and both static private
|
||||
* keys, so keys derived from the shared secret should not be used if
|
||||
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
|
||||
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
* <p>
|
||||
*
|
||||
* @param label A namespaced label indicating the purpose of this shared
|
||||
* secret, to prevent it from being repurposed or colliding with a shared
|
||||
* secret derived for another purpose
|
||||
* @param theirStaticPublicKey The static public key of the remote party
|
||||
* @param theirEphemeralPublicKey The ephemeral public key of the remote
|
||||
* party
|
||||
* @param ourStaticKeyPair The static key pair of the local party
|
||||
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
|
||||
* @param alice True if the local party is Alice
|
||||
* @param inputs Additional inputs that will be included in the
|
||||
* derivation of the shared secret
|
||||
* @return The shared secret
|
||||
*/
|
||||
@Deprecated
|
||||
SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,27 @@ import java.util.TreeMap;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A BDF dictionary contains zero or more key-value pairs, where the keys
|
||||
* are strings and the values are BDF objects, which may be primitive types
|
||||
* (null, boolean, integer, float, string, raw) or nested containers (list,
|
||||
* dictionary).
|
||||
* <p>
|
||||
* Note that a BDF integer has the same range as a Java long, while a BDF
|
||||
* float has the same range as a Java double. Method names in this class
|
||||
* correspond to the Java types.
|
||||
* <p>
|
||||
* The getX() methods throw {@link FormatException} if the specified key is
|
||||
* absent, the value is null, or the value does not have the requested type.
|
||||
* <p>
|
||||
* The getOptionalX() methods return null if the specified key is absent or
|
||||
* the value is null, or throw {@link FormatException} if the value does not
|
||||
* have the requested type.
|
||||
* <p>
|
||||
* The getX() methods that take a default value return the default value if
|
||||
* the specified key is absent or the value is null, or throw
|
||||
* {@link FormatException} if the value does not have the requested type.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public final class BdfDictionary extends TreeMap<String, Object> {
|
||||
|
||||
@@ -80,12 +101,33 @@ public final class BdfDictionary extends TreeMap<String, Object> {
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer with the specified key.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getLong(key).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if there is no value at the specified key,
|
||||
* or if the value is null or cannot be represented as a Java int.
|
||||
*/
|
||||
public Integer getInt(String key) throws FormatException {
|
||||
Integer value = getOptionalInt(key);
|
||||
if (value == null) throw new FormatException();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer with the specified key, or null if the key is
|
||||
* absent or the value is null.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getOptionalLong(key).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if the value at the specified key is not null
|
||||
* and cannot be represented as a Java int.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getOptionalInt(String key) throws FormatException {
|
||||
Long value = getOptionalLong(key);
|
||||
@@ -96,6 +138,17 @@ public final class BdfDictionary extends TreeMap<String, Object> {
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer with the specified key, or the given default
|
||||
* value if the key is absent or the value is null.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getLong(key, defaultValue).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if the value at the specified key is not null
|
||||
* and cannot be represented as a Java int.
|
||||
*/
|
||||
public Integer getInt(String key, Integer defaultValue)
|
||||
throws FormatException {
|
||||
Integer value = getOptionalInt(key);
|
||||
|
||||
@@ -6,6 +6,11 @@ import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A convenience class for building {@link BdfDictionary BdfDictionaries}
|
||||
* via the {@link BdfDictionary#of(Entry[]) factory method}. Entries in
|
||||
* BdfDictionaries do not have to be BdfEntries.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BdfEntry implements Entry<String, Object>, Comparable<BdfEntry> {
|
||||
|
||||
@@ -12,6 +12,29 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
|
||||
/**
|
||||
* A BDF list contains zero or more BDF objects, which may be primitive types
|
||||
* (null, boolean, integer, float, string, raw) or nested containers (list,
|
||||
* dictionary).
|
||||
* <p>
|
||||
* Note that a BDF integer has the same range as a Java long, while a BDF
|
||||
* float has the same range as a Java double. Method names in this class
|
||||
* correspond to the Java types.
|
||||
* <p>
|
||||
* The getX() methods throw {@link FormatException} if the object at the
|
||||
* specified index is null or does not have the requested type.
|
||||
* <p>
|
||||
* The getOptionalX() methods return null if the object at the specified
|
||||
* index is null, or throw {@link FormatException} if the object does not
|
||||
* have the requested type.
|
||||
* <p>
|
||||
* The getX() methods that take a default value return the default value if
|
||||
* the object at the specified index is null, or throw
|
||||
* {@link FormatException} if the object does not have the requested type.
|
||||
* <p>
|
||||
* All of the getters throw {@link FormatException} if the specified index is
|
||||
* out of range.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public final class BdfList extends ArrayList<Object> {
|
||||
|
||||
@@ -82,12 +105,34 @@ public final class BdfList extends ArrayList<Object> {
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer at the specified index.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getLong(index).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if the index is out of range, or if the
|
||||
* value at the specified index is null or cannot be represented as a
|
||||
* Java int.
|
||||
*/
|
||||
public Integer getInt(int index) throws FormatException {
|
||||
Integer value = getOptionalInt(index);
|
||||
if (value == null) throw new FormatException();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer at the specified index, or null if the object at
|
||||
* the specified index is null.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getOptionalLong(index).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if the index is out of range, or if the value
|
||||
* at the specified index cannot be represented as a Java int.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getOptionalInt(int index) throws FormatException {
|
||||
Long value = getOptionalLong(index);
|
||||
@@ -98,6 +143,17 @@ public final class BdfList extends ArrayList<Object> {
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer at the specified index, or the given default value
|
||||
* if the object at the specified index is null.
|
||||
* <p>
|
||||
* This method should be used in preference to
|
||||
* <code>getLong(index, defaultValue).intValue()</code> as it checks for
|
||||
* overflow/underflow.
|
||||
*
|
||||
* @throws FormatException if the index is out of range, or if the value
|
||||
* at the specified index cannot be represented as a Java int.
|
||||
*/
|
||||
public Integer getInt(int index, Integer defaultValue)
|
||||
throws FormatException {
|
||||
Integer value = getOptionalInt(index);
|
||||
|
||||
@@ -1,70 +1,178 @@
|
||||
package org.briarproject.bramble.api.data;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An interface for reading BDF objects from an input stream.
|
||||
* <p>
|
||||
* The readX() methods throw {@link FormatException} if the data is not in
|
||||
* canonical form, but the hasX() and skipX() methods do not check for
|
||||
* canonical form.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface BdfReader {
|
||||
|
||||
int DEFAULT_NESTED_LIMIT = 5;
|
||||
int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Returns true if the reader has reached the end of its input stream.
|
||||
*/
|
||||
boolean eof() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the reader's input stream.
|
||||
*/
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF null.
|
||||
*/
|
||||
boolean hasNull() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF null from the input.
|
||||
*/
|
||||
void readNull() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF null.
|
||||
*/
|
||||
void skipNull() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF boolean.
|
||||
*/
|
||||
boolean hasBoolean() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF boolean from the input and returns it.
|
||||
*/
|
||||
boolean readBoolean() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF boolean.
|
||||
*/
|
||||
void skipBoolean() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF integer, which
|
||||
* has the same range as a Java long.
|
||||
*/
|
||||
boolean hasLong() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF integer from the input and returns it as a Java long.
|
||||
*/
|
||||
long readLong() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF integer.
|
||||
*/
|
||||
void skipLong() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF integer and the
|
||||
* value would fit within the range of a Java int.
|
||||
*/
|
||||
boolean hasInt() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF integer from the input and returns it as a Java int.
|
||||
*
|
||||
* @throws FormatException if the value exceeds the range of a Java int.
|
||||
*/
|
||||
int readInt() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF integer.
|
||||
*
|
||||
* @throws FormatException if the value exceeds the range of a Java int.
|
||||
*/
|
||||
void skipInt() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF float, which has
|
||||
* the same range as a Java double.
|
||||
*/
|
||||
boolean hasDouble() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF float from the input and returns it as a Java double.
|
||||
*/
|
||||
double readDouble() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF float.
|
||||
*/
|
||||
void skipDouble() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF string.
|
||||
*/
|
||||
boolean hasString() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF string from the input.
|
||||
*
|
||||
* @throws IOException If the string is not valid UTF-8.
|
||||
*/
|
||||
String readString() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF string without checking whether it is valid UTF-8.
|
||||
*/
|
||||
void skipString() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF raw.
|
||||
*/
|
||||
boolean hasRaw() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF raw from the input and returns it as a byte array.
|
||||
*/
|
||||
byte[] readRaw() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF raw.
|
||||
*/
|
||||
void skipRaw() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF list.
|
||||
*/
|
||||
boolean hasList() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF list from the input and returns it. The list's contents
|
||||
* are parsed and validated.
|
||||
*/
|
||||
BdfList readList() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF list. The list's contents are parsed (to determine
|
||||
* their length) but not validated.
|
||||
*/
|
||||
void skipList() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the next object in the input is a BDF dictionary.
|
||||
*/
|
||||
boolean hasDictionary() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a BDF dictionary from the input and returns it. The dictionary's
|
||||
* contents are parsed and validated.
|
||||
*/
|
||||
BdfDictionary readDictionary() throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over a BDF dictionary. The dictionary's contents are parsed
|
||||
* (to determine their length) but not validated.
|
||||
*/
|
||||
void skipDictionary() throws IOException;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ public interface BdfReaderFactory {
|
||||
|
||||
BdfReader createReader(InputStream in);
|
||||
|
||||
/**
|
||||
* Transitional alternative to {@link #createReader(InputStream)} that
|
||||
* can create a reader that accepts non-canonical input, for backward
|
||||
* compatibility.
|
||||
*/
|
||||
@Deprecated
|
||||
BdfReader createReader(InputStream in, boolean canonical);
|
||||
|
||||
BdfReader createReader(InputStream in, int nestedLimit,
|
||||
|
||||
@@ -1,28 +1,74 @@
|
||||
package org.briarproject.bramble.api.data;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An interface for writing BDF objects to an output stream. The BDF output
|
||||
* is in canonical form, ie integers and length fields are represented using
|
||||
* the minimum number of bytes and dictionary keys are unique and sorted in
|
||||
* lexicographic order.
|
||||
*/
|
||||
public interface BdfWriter {
|
||||
|
||||
/**
|
||||
* Flushes the writer's output stream.
|
||||
*/
|
||||
void flush() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the writer's output stream.
|
||||
*/
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF null to the output stream.
|
||||
*/
|
||||
void writeNull() throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF boolean to the output stream.
|
||||
*/
|
||||
void writeBoolean(boolean b) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF integer (which has the same range as a Java long) to the
|
||||
* output stream.
|
||||
*/
|
||||
void writeLong(long l) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF float (which has the same range as a Java double) to the
|
||||
* output stream.
|
||||
*/
|
||||
void writeDouble(double d) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF string (which uses UTF-8 encoding) to the output stream.
|
||||
*/
|
||||
void writeString(String s) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF raw to the output stream.
|
||||
*/
|
||||
void writeRaw(byte[] b) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF list to the output stream.
|
||||
*
|
||||
* @throws FormatException if the contents of the given collection cannot
|
||||
* be represented as (nested) BDF objects.
|
||||
*/
|
||||
void writeList(Collection<?> c) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a BDF dictionary to the output stream.
|
||||
*
|
||||
* @throws FormatException if the contents of the given map cannot be
|
||||
* represented as (nested) BDF objects.
|
||||
*/
|
||||
void writeDictionary(Map<?, ?> m) throws IOException;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.network;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@@ -27,4 +28,20 @@ public class NetworkStatus {
|
||||
public boolean isIpv6Only() {
|
||||
return ipv6Only;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (connected ? 1 : 0) | (wifi ? 2 : 0) | (ipv6Only ? 4 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (o instanceof NetworkStatus) {
|
||||
NetworkStatus s = (NetworkStatus) o;
|
||||
return connected == s.connected
|
||||
&& wifi == s.wifi
|
||||
&& ipv6Only == s.ipv6Only;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public interface TorConstants {
|
||||
|
||||
/**
|
||||
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
|
||||
* Currently unused, but may be worth keeping for future use.
|
||||
*/
|
||||
int REASON_COUNTRY_BLOCKED = 8;
|
||||
}
|
||||
|
||||
@@ -32,8 +32,15 @@ public interface RecordReader {
|
||||
* 'accept' or 'ignore' predicates
|
||||
*/
|
||||
@Nullable
|
||||
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
||||
Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||
throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Interface that reifies the generic interface {@code Predicate<Record>}
|
||||
* for easier testing.
|
||||
*/
|
||||
interface RecordPredicate extends Predicate<Record> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.util.StringUtils.startsWithIgnoreCase;
|
||||
|
||||
@NotNullByDefault
|
||||
public class OsUtils {
|
||||
|
||||
@@ -13,15 +15,15 @@ public class OsUtils {
|
||||
private static final String vendor = System.getProperty("java.vendor");
|
||||
|
||||
public static boolean isWindows() {
|
||||
return os != null && os.contains("Windows");
|
||||
return os != null && startsWithIgnoreCase(os, "Win");
|
||||
}
|
||||
|
||||
public static boolean isMac() {
|
||||
return os != null && os.contains("Mac OS");
|
||||
return os != null && os.equalsIgnoreCase("Mac OS X");
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return os != null && os.contains("Linux") && !isAndroid();
|
||||
return os != null && startsWithIgnoreCase(os, "Linux") && !isAndroid();
|
||||
}
|
||||
|
||||
public static boolean isAndroid() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class PrivacyUtils {
|
||||
}
|
||||
|
||||
private static String scrubIpv6Address(byte[] ipv6) {
|
||||
String hex = toHexString(ipv6).toLowerCase();
|
||||
String hex = toHexString(ipv6).toLowerCase(Locale.US);
|
||||
return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,4 +176,9 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
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.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.43.2:dagger-2.43.2.jar:c89681f7cbbf8c527bf4ac2748515d617fdb54a1d425c08d914fdc28192b5fe4',
|
||||
'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',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'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.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',
|
||||
'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.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.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
]
|
||||
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.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',
|
||||
'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',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'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.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.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.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',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ apply from: '../dagger.gradle'
|
||||
dependencies {
|
||||
api project(':bramble-api')
|
||||
|
||||
api 'org.briarproject:jtorctl:0.5'
|
||||
api "org.briarproject:onionwrapper-core:$onionwrapper_version"
|
||||
|
||||
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
|
||||
//noinspection GradleDependency
|
||||
@@ -28,7 +28,7 @@ dependencies {
|
||||
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.2'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.6'
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||
@@ -24,6 +23,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
getLogger(ContactExchangeManagerImpl.class.getName());
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
interface HandshakeConstants {
|
||||
|
||||
/**
|
||||
* The current version of the handshake protocol.
|
||||
* The current major version of the handshake protocol.
|
||||
*/
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
byte PROTOCOL_MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key.
|
||||
* The current minor version of the handshake protocol.
|
||||
*/
|
||||
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
byte PROTOCOL_MINOR_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the deprecated v0.0 key
|
||||
* derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*/
|
||||
@Deprecated
|
||||
String MASTER_KEY_LABEL_0_0 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the v0.1 key derivation
|
||||
* method.
|
||||
*/
|
||||
String MASTER_KEY_LABEL_0_1 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's proof of ownership from the master key.
|
||||
|
||||
@@ -13,11 +13,26 @@ interface HandshakeCrypto {
|
||||
KeyPair generateEphemeralKeyPair();
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys.
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the deprecated v0.0 key derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the v0.1 key derivation method.
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
@@ -13,7 +13,8 @@ import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
||||
alice, inputs);
|
||||
return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
byte[] theirStatic = theirStaticPublicKey.getEncoded();
|
||||
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
|
||||
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
|
||||
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
|
||||
byte[][] inputs = {
|
||||
alice ? ourStatic : theirStatic,
|
||||
alice ? theirStatic : ourStatic,
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL_0_1,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
@@ -12,12 +11,12 @@ import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -28,15 +27,20 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
|
||||
@Immutable
|
||||
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
class HandshakeManagerImpl implements HandshakeManager {
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
private static boolean isKnownRecordType(byte type) {
|
||||
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
||||
return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
|
||||
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
|
||||
type == RECORD_TYPE_MINOR_VERSION;
|
||||
}
|
||||
|
||||
private final TransactionManager db;
|
||||
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
HandshakeManagerImpl(DatabaseComponent db,
|
||||
HandshakeManagerImpl(TransactionManager db,
|
||||
IdentityManager identityManager,
|
||||
ContactManager contactManager,
|
||||
TransportCrypto transportCrypto,
|
||||
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
.createRecordWriter(out.getOutputStream());
|
||||
KeyPair ourEphemeralKeyPair =
|
||||
handshakeCrypto.generateEphemeralKeyPair();
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
Pair<Byte, PublicKey> theirMinorVersionAndKey;
|
||||
if (alice) {
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
} else {
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
}
|
||||
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
|
||||
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
|
||||
SecretKey masterKey;
|
||||
try {
|
||||
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
if (theirMinorVersion > 0) {
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_1(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
} else {
|
||||
// TODO: Remove this branch after a reasonable migration
|
||||
// period (added 2023-03-10).
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_0(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
}
|
||||
|
||||
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
||||
k.getEncoded()));
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
||||
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload();
|
||||
/**
|
||||
* Receives the remote peer's protocol minor version and ephemeral public
|
||||
* key.
|
||||
* <p>
|
||||
* In version 0.1 of the protocol, each peer sends a minor version record
|
||||
* followed by an ephemeral public key record.
|
||||
* <p>
|
||||
* In version 0.0 of the protocol, each peer sends an ephemeral public key
|
||||
* record without a preceding minor version record.
|
||||
* <p>
|
||||
* Therefore the remote peer's minor version must be non-zero if a minor
|
||||
* version record is received, and is assumed to be zero if no minor
|
||||
* version record is received.
|
||||
*/
|
||||
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
|
||||
throws IOException {
|
||||
byte theirMinorVersion;
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
// The first record can be either a minor version record or an
|
||||
// ephemeral public key record
|
||||
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
|
||||
// The payload must be a single byte giving the remote peer's
|
||||
// protocol minor version, which must be non-zero
|
||||
byte[] payload = first.getPayload();
|
||||
checkLength(payload, 1);
|
||||
theirMinorVersion = payload[0];
|
||||
if (theirMinorVersion == 0) throw new FormatException();
|
||||
// The second record must be an ephemeral public key record
|
||||
Record second = readRecord(r,
|
||||
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
theirEphemeralPublicKey = parsePublicKey(second);
|
||||
} else {
|
||||
// The remote peer did not send a minor version record, so the
|
||||
// remote peer's protocol minor version is assumed to be zero
|
||||
// TODO: Remove this branch after a reasonable migration period
|
||||
// (added 2023-03-10).
|
||||
theirMinorVersion = 0;
|
||||
theirEphemeralPublicKey = parsePublicKey(first);
|
||||
}
|
||||
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
|
||||
}
|
||||
|
||||
private PublicKey parsePublicKey(Record rec) throws IOException {
|
||||
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] key = rec.getPayload();
|
||||
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
return new AgreementPublicKey(key);
|
||||
}
|
||||
|
||||
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
||||
Record rec = readRecord(r,
|
||||
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
|
||||
byte[] proof = rec.getPayload();
|
||||
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
||||
return proof;
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, byte expectedType)
|
||||
private void sendMinorVersion(RecordWriter w) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
|
||||
throws IOException {
|
||||
// Accept records with current protocol version, expected type only
|
||||
Predicate<Record> accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
rec.getRecordType() == expectedType;
|
||||
// Accept records with current protocol version, expected types only
|
||||
RecordPredicate accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
|
||||
expectedTypes.contains(rec.getRecordType());
|
||||
Record rec = r.readRecord(accept, IGNORE);
|
||||
if (rec == null) throw new EOFException();
|
||||
return rec;
|
||||
|
||||
@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
|
||||
*/
|
||||
interface HandshakeRecordTypes {
|
||||
|
||||
byte EPHEMERAL_PUBLIC_KEY = 0;
|
||||
byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
|
||||
|
||||
byte PROOF_OF_OWNERSHIP = 1;
|
||||
byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
|
||||
|
||||
byte RECORD_TYPE_MINOR_VERSION = 2;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -222,7 +223,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
@Deprecated
|
||||
public SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
@@ -250,6 +252,35 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
|
||||
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
|
||||
byte[][] hashInputs = new byte[inputs.length + 3][];
|
||||
// Alice ephemeral/Bob ephemeral
|
||||
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
|
||||
if (alice) {
|
||||
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
} else {
|
||||
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
}
|
||||
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
|
||||
byte[] hash = hash(label, hashInputs);
|
||||
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
|
||||
throws GeneralSecurityException {
|
||||
@@ -470,7 +501,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
arraycopy(publicKey, 0, address, 0, publicKey.length);
|
||||
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
|
||||
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
|
||||
return Base32.encode(address).toLowerCase();
|
||||
return Base32.encode(address).toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
|
||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -76,14 +75,11 @@ public class MailboxModule {
|
||||
ValidationManager validationManager,
|
||||
ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder,
|
||||
Clock clock,
|
||||
FeatureFlags featureFlags) {
|
||||
Clock clock) {
|
||||
MailboxUpdateValidator validator = new MailboxUpdateValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
if (featureFlags.shouldEnableMailbox()) {
|
||||
validationManager.registerMessageValidator(CLIENT_ID,
|
||||
MAJOR_VERSION, validator);
|
||||
}
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@@ -95,31 +91,26 @@ public class MailboxModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxUpdateManager provideMailboxUpdateManager(
|
||||
FeatureFlags featureFlags,
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager, ContactManager contactManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxUpdateManagerImpl 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);
|
||||
}
|
||||
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(FeatureFlags featureFlags,
|
||||
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) {
|
||||
if (featureFlags.shouldEnableMailbox()) {
|
||||
eventBus.addListener(mailboxFileManager);
|
||||
}
|
||||
MailboxFileManager provideMailboxFileManager(EventBus eventBus,
|
||||
MailboxFileManagerImpl mailboxFileManager) {
|
||||
eventBus.addListener(mailboxFileManager);
|
||||
return mailboxFileManager;
|
||||
}
|
||||
|
||||
@@ -160,17 +151,14 @@ 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);
|
||||
if (featureFlags.shouldEnableMailbox()) {
|
||||
lifecycleManager.registerService(manager);
|
||||
eventBus.addListener(manager);
|
||||
}
|
||||
lifecycleManager.registerService(manager);
|
||||
eventBus.addListener(manager);
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -288,8 +288,10 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
private class Callback implements PluginCallback {
|
||||
|
||||
private final TransportId id;
|
||||
private final AtomicReference<State> state =
|
||||
new AtomicReference<>(STARTING_STOPPING);
|
||||
private final Object stateLock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = STARTING_STOPPING;
|
||||
|
||||
private Callback(TransportId id) {
|
||||
this.id = id;
|
||||
@@ -343,22 +345,26 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
|
||||
@Override
|
||||
public void pluginStateChanged(State newState) {
|
||||
State oldState = state.getAndSet(newState);
|
||||
if (newState != oldState) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(id + " changed from state " + oldState
|
||||
+ " to " + newState);
|
||||
synchronized (stateLock) {
|
||||
if (newState != state) {
|
||||
State oldState = state;
|
||||
state = newState;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(id + " changed from state " + oldState
|
||||
+ " to " + newState);
|
||||
}
|
||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||
if (newState == ACTIVE) {
|
||||
eventBus.broadcast(new TransportActiveEvent(id));
|
||||
} else if (oldState == ACTIVE) {
|
||||
eventBus.broadcast(new TransportInactiveEvent(id));
|
||||
}
|
||||
} else if (newState == DISABLED) {
|
||||
// Broadcast an event even though the state hasn't changed,
|
||||
// as the reasons for the plugin being disabled may have
|
||||
// changed
|
||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||
}
|
||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||
if (newState == ACTIVE) {
|
||||
eventBus.broadcast(new TransportActiveEvent(id));
|
||||
} else if (oldState == ACTIVE) {
|
||||
eventBus.broadcast(new TransportInactiveEvent(id));
|
||||
}
|
||||
} else if (newState == DISABLED) {
|
||||
// Broadcast an event even though the state hasn't changed, as
|
||||
// the reasons for the plugin being disabled may have changed
|
||||
eventBus.broadcast(new TransportStateEvent(id, newState));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
import org.briarproject.onionwrapper.CircumventionProviderFactory;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -10,8 +13,7 @@ public class CircumventionModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CircumventionProvider provideCircumventionProvider(
|
||||
CircumventionProviderImpl provider) {
|
||||
return provider;
|
||||
CircumventionProvider provideCircumventionProvider() {
|
||||
return CircumventionProviderFactory.createCircumventionProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
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.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));
|
||||
|
||||
@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 = getSnowflakeParams(countryCode, letsEncrypt);
|
||||
bridges.add(line.substring(2) + " " + params);
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
return bridges;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
|
||||
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, letsEncrypt));
|
||||
if (value != null) return value;
|
||||
// Return the default parameters
|
||||
value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
|
||||
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, digit
|
||||
String value = line.substring(5);
|
||||
params.put(key, value);
|
||||
}
|
||||
scanner.close();
|
||||
return params;
|
||||
}
|
||||
|
||||
private String makeKey(String countryCode, boolean letsEncrypt) {
|
||||
return countryCode + " " + (letsEncrypt ? "1" : "0");
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
@@ -27,30 +24,23 @@ import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.InterfaceNotNullByDefault;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
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 java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
@@ -63,13 +53,9 @@ import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
||||
@@ -89,38 +75,20 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
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.UTF_8;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@InterfaceNotNullByDefault
|
||||
class TorPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC",
|
||||
"ORCONN",
|
||||
"STATUS_GENERAL",
|
||||
"STATUS_CLIENT",
|
||||
"HS_DESC",
|
||||
"NOTICE",
|
||||
"WARN",
|
||||
"ERR"
|
||||
};
|
||||
private static final String OWNER = "__OwningControllerProcess";
|
||||
private static final int COOKIE_TIMEOUT_MS = 3000;
|
||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||
|
||||
protected final Executor ioExecutor;
|
||||
@@ -129,91 +97,76 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final NetworkManager networkManager;
|
||||
private final LocationUtils locationUtils;
|
||||
private final SocketFactory torSocketFactory;
|
||||
private final Clock clock;
|
||||
private final CircumventionProvider circumventionProvider;
|
||||
private final BatteryManager batteryManager;
|
||||
private final Backoff backoff;
|
||||
private final TorRendezvousCrypto torRendezvousCrypto;
|
||||
private final TorWrapper tor;
|
||||
private final PluginCallback callback;
|
||||
private final String architecture;
|
||||
private final CircumventionProvider circumventionProvider;
|
||||
private final ResourceProvider resourceProvider;
|
||||
private final long maxLatency;
|
||||
private final int maxIdleTime;
|
||||
private final int socketTimeout;
|
||||
private final File torDirectory;
|
||||
private final File configFile;
|
||||
private final int torSocksPort;
|
||||
private final int torControlPort;
|
||||
private final File doneFile, cookieFile;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
protected final PluginState state = new PluginState();
|
||||
|
||||
private volatile Socket controlSocket = null;
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile Settings settings = null;
|
||||
|
||||
protected abstract int getProcessId();
|
||||
|
||||
protected abstract long getLastUpdateTime();
|
||||
|
||||
TorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
TorWrapper tor,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
int maxIdleTime) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
this.networkManager = networkManager;
|
||||
this.locationUtils = locationUtils;
|
||||
this.torSocketFactory = torSocketFactory;
|
||||
this.clock = clock;
|
||||
this.resourceProvider = resourceProvider;
|
||||
this.circumventionProvider = circumventionProvider;
|
||||
this.batteryManager = batteryManager;
|
||||
this.backoff = backoff;
|
||||
this.torRendezvousCrypto = torRendezvousCrypto;
|
||||
this.tor = tor;
|
||||
this.callback = callback;
|
||||
this.architecture = architecture;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2) {
|
||||
socketTimeout = Integer.MAX_VALUE;
|
||||
else socketTimeout = maxIdleTime * 2;
|
||||
this.torDirectory = torDirectory;
|
||||
this.torSocksPort = torSocksPort;
|
||||
this.torControlPort = torControlPort;
|
||||
configFile = new File(torDirectory, "torrc");
|
||||
doneFile = new File(torDirectory, "done");
|
||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||
} else {
|
||||
socketTimeout = maxIdleTime * 2;
|
||||
}
|
||||
// Don't execute more than one connection status check at a time
|
||||
connectionStatusExecutor =
|
||||
new PoliteExecutor("TorPlugin", ioExecutor, 1);
|
||||
}
|
||||
tor.setObserver(new Observer() {
|
||||
|
||||
protected File getTorExecutableFile() {
|
||||
return new File(torDirectory, "tor");
|
||||
}
|
||||
@Override
|
||||
public void onState(TorState torState) {
|
||||
State s = state.getState(torState);
|
||||
if (s == ACTIVE) backoff.reset();
|
||||
callback.pluginStateChanged(s);
|
||||
}
|
||||
|
||||
protected File getObfs4ExecutableFile() {
|
||||
return new File(torDirectory, "obfs4proxy");
|
||||
}
|
||||
@Override
|
||||
public void onBootstrapPercentage(int percentage) {
|
||||
}
|
||||
|
||||
protected File getSnowflakeExecutableFile() {
|
||||
return new File(torDirectory, "snowflake");
|
||||
@Override
|
||||
public void onHsDescriptorUpload(String onion) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClockSkewDetected(long skewSeconds) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,89 +187,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
if (!torDirectory.exists()) {
|
||||
if (!torDirectory.mkdirs()) {
|
||||
LOG.warning("Could not create Tor directory.");
|
||||
throw new PluginException();
|
||||
}
|
||||
}
|
||||
// Load the settings
|
||||
settings = callback.getSettings();
|
||||
// Start Tor
|
||||
try {
|
||||
// Install or update the assets if necessary
|
||||
if (!assetsAreUpToDate()) installAssets();
|
||||
// Start from the default config every time
|
||||
extract(getConfigInputStream(), configFile);
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
if (cookieFile.exists() && !cookieFile.delete())
|
||||
LOG.warning("Old auth cookie not deleted");
|
||||
// Start a new Tor process
|
||||
LOG.info("Starting Tor");
|
||||
File torFile = getTorExecutableFile();
|
||||
String torPath = torFile.getAbsolutePath();
|
||||
String configPath = configFile.getAbsolutePath();
|
||||
String pid = String.valueOf(getProcessId());
|
||||
Process torProcess;
|
||||
ProcessBuilder pb =
|
||||
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
|
||||
Map<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");
|
||||
tor.start();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while starting Tor");
|
||||
Thread.currentThread().interrupt();
|
||||
throw new PluginException();
|
||||
}
|
||||
try {
|
||||
// Open a control connection and authenticate using the cookie file
|
||||
controlSocket = new Socket("127.0.0.1", torControlPort);
|
||||
controlConnection = new TorControlConnection(controlSocket);
|
||||
controlConnection.authenticate(read(cookieFile));
|
||||
// Tell Tor to exit when the control connection is closed
|
||||
controlConnection.takeOwnership();
|
||||
controlConnection.resetConf(singletonList(OWNER));
|
||||
// Register to receive events from the Tor process
|
||||
controlConnection.setEventHandler(this);
|
||||
controlConnection.setEvents(asList(EVENTS));
|
||||
// Check whether Tor has already bootstrapped
|
||||
String info = controlConnection.getInfo("status/bootstrap-phase");
|
||||
if (info != null && info.contains("PROGRESS=100")) {
|
||||
LOG.info("Tor has already bootstrapped");
|
||||
state.setBootstrapped();
|
||||
}
|
||||
// Check whether Tor has already built a circuit
|
||||
info = controlConnection.getInfo("status/circuit-established");
|
||||
if ("1".equals(info)) {
|
||||
LOG.info("Tor has already built a circuit");
|
||||
state.setCircuitBuilt(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
state.setStarted();
|
||||
// Check whether we're online
|
||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||
batteryManager.isCharging());
|
||||
@@ -324,130 +206,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
bind();
|
||||
}
|
||||
|
||||
private boolean assetsAreUpToDate() {
|
||||
return doneFile.lastModified() > getLastUpdateTime();
|
||||
}
|
||||
|
||||
private void installAssets() throws IOException {
|
||||
// The done file may already exist from a previous installation
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
doneFile.delete();
|
||||
installTorExecutable();
|
||||
installObfs4Executable();
|
||||
installSnowflakeExecutable();
|
||||
if (!doneFile.createNewFile())
|
||||
LOG.warning("Failed to create done file");
|
||||
}
|
||||
|
||||
protected void extract(InputStream in, File dest) throws IOException {
|
||||
OutputStream out = new FileOutputStream(dest);
|
||||
copyAndClose(in, out);
|
||||
}
|
||||
|
||||
protected void installTorExecutable() throws IOException {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Installing Tor binary for " + architecture);
|
||||
File torFile = getTorExecutableFile();
|
||||
extract(getExecutableInputStream("tor"), torFile);
|
||||
if (!torFile.setExecutable(true, true)) throw new IOException();
|
||||
}
|
||||
|
||||
protected void installObfs4Executable() throws IOException {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Installing obfs4proxy binary for " + architecture);
|
||||
File obfs4File = getObfs4ExecutableFile();
|
||||
extract(getExecutableInputStream("obfs4proxy"), obfs4File);
|
||||
if (!obfs4File.setExecutable(true, true)) throw new IOException();
|
||||
}
|
||||
|
||||
protected void installSnowflakeExecutable() throws IOException {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Installing snowflake binary for " + architecture);
|
||||
File snowflakeFile = getSnowflakeExecutableFile();
|
||||
extract(getExecutableInputStream("snowflake"), snowflakeFile);
|
||||
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
|
||||
}
|
||||
|
||||
private InputStream getExecutableInputStream(String basename) {
|
||||
String ext = getExecutableExtension();
|
||||
return requireNonNull(resourceProvider
|
||||
.getResourceInputStream(architecture + "/" + basename, ext));
|
||||
}
|
||||
|
||||
protected String getExecutableExtension() {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static void append(StringBuilder strb, String name, Object value) {
|
||||
strb.append(name);
|
||||
strb.append(" ");
|
||||
strb.append(value);
|
||||
strb.append("\n");
|
||||
}
|
||||
|
||||
private InputStream getConfigInputStream() {
|
||||
File dataDirectory = new File(torDirectory, ".tor");
|
||||
StringBuilder strb = new StringBuilder();
|
||||
append(strb, "ControlPort", torControlPort);
|
||||
append(strb, "CookieAuthentication", 1);
|
||||
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||
append(strb, "DisableNetwork", 1);
|
||||
append(strb, "RunAsDaemon", 1);
|
||||
append(strb, "SafeSocks", 1);
|
||||
append(strb, "SocksPort", torSocksPort);
|
||||
strb.append("GeoIPFile\n");
|
||||
strb.append("GeoIPv6File\n");
|
||||
append(strb, "ConnectionPadding", 0);
|
||||
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
||||
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
||||
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
|
||||
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
|
||||
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
|
||||
return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
|
||||
}
|
||||
|
||||
private void listFiles(File f) {
|
||||
if (f.isDirectory()) {
|
||||
File[] children = f.listFiles();
|
||||
if (children != null) for (File child : children) listFiles(child);
|
||||
} else {
|
||||
LOG.info(f.getAbsolutePath() + " " + f.length());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] read(File f) throws IOException {
|
||||
byte[] b = new byte[(int) f.length()];
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
try {
|
||||
int offset = 0;
|
||||
while (offset < b.length) {
|
||||
int read = in.read(b, offset, b.length - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
return b;
|
||||
} finally {
|
||||
tryToClose(in, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Read the process's stdout (and redirected stderr) until it detaches
|
||||
while (stdout.hasNextLine()) stdout.nextLine();
|
||||
stdout.close();
|
||||
// Wait for the process to detach or exit
|
||||
int exit = torProcess.waitFor();
|
||||
if (exit != 0) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Tor exited with value " + exit);
|
||||
throw new PluginException();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
@@ -471,9 +229,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return;
|
||||
}
|
||||
// Store the port number
|
||||
String localPort = String.valueOf(ss.getLocalPort());
|
||||
int localPort = ss.getLocalPort();
|
||||
Settings s = new Settings();
|
||||
s.put(PREF_TOR_PORT, localPort);
|
||||
s.put(PREF_TOR_PORT, String.valueOf(localPort));
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(() -> publishHiddenService(localPort));
|
||||
@@ -483,48 +241,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void publishHiddenService(String port) {
|
||||
if (!state.isTorRunning()) return;
|
||||
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
|
||||
publishV3HiddenService(port, privKey3);
|
||||
}
|
||||
|
||||
private void publishV3HiddenService(String port, @Nullable String privKey) {
|
||||
private void publishHiddenService(int localPort) {
|
||||
if (!tor.isTorRunning()) return;
|
||||
String privKey = settings.get(HS_PRIVATE_KEY_V3);
|
||||
LOG.info("Creating v3 hidden service");
|
||||
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
|
||||
Map<String, String> response;
|
||||
HiddenServiceProperties hsProps;
|
||||
try {
|
||||
// Use the control connection to set up the hidden service
|
||||
if (privKey == null) {
|
||||
response = controlConnection.addOnion("NEW:ED25519-V3",
|
||||
portLines, null);
|
||||
} else {
|
||||
response = controlConnection.addOnion(privKey, portLines);
|
||||
}
|
||||
hsProps = tor.publishHiddenService(localPort, 80, privKey);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return;
|
||||
}
|
||||
if (!response.containsKey(HS_ADDRESS)) {
|
||||
LOG.warning("Tor did not return a hidden service address");
|
||||
return;
|
||||
}
|
||||
if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
|
||||
LOG.warning("Tor did not return a private key");
|
||||
return;
|
||||
}
|
||||
String onion3 = response.get(HS_ADDRESS);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("V3 hidden service " + scrubOnion(onion3));
|
||||
LOG.info("V3 hidden service " + scrubOnion(hsProps.onion));
|
||||
}
|
||||
if (privKey == null) {
|
||||
// Publish the hidden service's onion hostname in transport props
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ONION_V3, onion3);
|
||||
p.put(PROP_ONION_V3, hsProps.onion);
|
||||
callback.mergeLocalProperties(p);
|
||||
// Save the hidden service's private key for next time
|
||||
Settings s = new Settings();
|
||||
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
|
||||
s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
|
||||
callback.mergeSettings(s);
|
||||
}
|
||||
}
|
||||
@@ -547,50 +285,31 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void enableNetwork(boolean enable) throws IOException {
|
||||
if (!state.enableNetwork(enable)) return; // Unchanged
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
}
|
||||
|
||||
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
|
||||
throws IOException {
|
||||
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
||||
if (bridgeTypes.isEmpty()) {
|
||||
controlConnection.setConf("UseBridges", "0");
|
||||
controlConnection.resetConf(singletonList("Bridge"));
|
||||
tor.disableBridges();
|
||||
} else {
|
||||
Collection<String> conf = new ArrayList<>();
|
||||
conf.add("UseBridges 1");
|
||||
boolean letsEncrypt = canVerifyLetsEncryptCerts();
|
||||
List<String> bridges = new ArrayList<>();
|
||||
for (BridgeType bridgeType : bridgeTypes) {
|
||||
conf.addAll(circumventionProvider
|
||||
.getBridges(bridgeType, countryCode, letsEncrypt));
|
||||
bridges.addAll(circumventionProvider.getBridges(bridgeType,
|
||||
countryCode));
|
||||
}
|
||||
controlConnection.setConf(conf);
|
||||
tor.enableBridges(bridges);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this device can verify Let's Encrypt certificates signed
|
||||
* with the IdentTrust DST Root X3 certificate, which expired at the end of
|
||||
* September 2021.
|
||||
*/
|
||||
protected boolean canVerifyLetsEncryptCerts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ServerSocket ss = state.setStopped();
|
||||
tryToClose(ss, LOG, WARNING);
|
||||
if (controlSocket != null && controlConnection != null) {
|
||||
try {
|
||||
LOG.info("Stopping Tor");
|
||||
controlConnection.shutdownTor("TERM");
|
||||
controlSocket.close();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
try {
|
||||
tor.stop();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while stopping Tor");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,6 +420,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
TransportProperties remoteProperties = new TransportProperties();
|
||||
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
||||
try {
|
||||
@SuppressWarnings("resource")
|
||||
ServerSocket ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", 0));
|
||||
int port = ss.getLocalPort();
|
||||
@@ -717,9 +437,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
LOG.info("Rendezvous server socket closed");
|
||||
}
|
||||
});
|
||||
Map<Integer, String> portLines =
|
||||
singletonMap(80, "127.0.0.1:" + port);
|
||||
controlConnection.addOnion(blob, portLines);
|
||||
tor.publishHiddenService(port, 80, blob);
|
||||
return new RendezvousEndpoint() {
|
||||
|
||||
@Override
|
||||
@@ -729,8 +447,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
controlConnection.delOnion(localOnion);
|
||||
tryToClose(ss, LOG, WARNING);
|
||||
try {
|
||||
tor.removeHiddenService(localOnion);
|
||||
} finally {
|
||||
tryToClose(ss, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (IOException e) {
|
||||
@@ -739,121 +460,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void circuitStatus(String status, String id, String path) {
|
||||
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamStatus(String status, String id, String target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
|
||||
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
||||
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newDescriptors(List<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()) {
|
||||
// TODO: Restart the Tor process
|
||||
LOG.warning("Control connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
private String removeSeverity(String msg) {
|
||||
return msg.replaceFirst("[^ ]+ ", "");
|
||||
}
|
||||
|
||||
private void handleClientStatus(String msg) {
|
||||
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
|
||||
LOG.info("Bootstrapped");
|
||||
state.setBootstrapped();
|
||||
backoff.reset();
|
||||
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
||||
if (state.setCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||
if (state.setCircuitBuilt(false)) {
|
||||
LOG.info("Circuit not built");
|
||||
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
||||
// its guard/bridge connections? This will also close any
|
||||
// established circuits, which might still be functioning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGeneralStatus(String msg) {
|
||||
if (msg.startsWith("CLOCK_JUMPED")) {
|
||||
Long time = parseLongArgument(msg, "TIME");
|
||||
if (time != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock jumped " + time + " seconds");
|
||||
}
|
||||
} else if (msg.startsWith("CLOCK_SKEW")) {
|
||||
Long skew = parseLongArgument(msg, "SKEW");
|
||||
if (skew != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock is skewed by " + skew + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long parseLongArgument(String msg, String argName) {
|
||||
String[] args = msg.split(" ");
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith(argName + "=")) {
|
||||
try {
|
||||
return Long.parseLong(arg.substring(argName.length() + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
@@ -876,13 +482,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private void updateConnectionStatus(NetworkStatus status,
|
||||
boolean charging) {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
if (!state.isTorRunning()) return;
|
||||
if (!tor.isTorRunning()) return;
|
||||
boolean online = status.isConnected();
|
||||
boolean wifi = status.isWifi();
|
||||
boolean ipv6Only = status.isIpv6Only();
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked =
|
||||
circumventionProvider.isTorProbablyBlocked(country);
|
||||
boolean bridgesByDefault =
|
||||
circumventionProvider.shouldUseBridges(country);
|
||||
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
int network = settings.getInt(PREF_TOR_NETWORK,
|
||||
@@ -892,7 +498,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
boolean onlyWhenCharging =
|
||||
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
|
||||
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING);
|
||||
boolean bridgesWork = circumventionProvider.doBridgesWork(country);
|
||||
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
@@ -922,10 +527,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
LOG.info("Configured not to use mobile data");
|
||||
reasonsDisabled |= REASON_MOBILE_DATA;
|
||||
}
|
||||
if (automatic && blocked && !bridgesWork) {
|
||||
LOG.info("Country is blocked");
|
||||
reasonsDisabled |= REASON_COUNTRY_BLOCKED;
|
||||
}
|
||||
|
||||
if (reasonsDisabled != 0) {
|
||||
LOG.info("Disabling network due to settings");
|
||||
@@ -933,7 +534,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork = true;
|
||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||
(automatic && bridgesWork)) {
|
||||
(automatic && bridgesByDefault)) {
|
||||
if (ipv6Only) {
|
||||
bridgeTypes = asList(MEEK, SNOWFLAKE);
|
||||
} else {
|
||||
@@ -960,41 +561,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
try {
|
||||
if (enableNetwork) {
|
||||
enableBridges(bridgeTypes, country);
|
||||
enableConnectionPadding(enableConnectionPadding);
|
||||
enableIpv6(ipv6Only);
|
||||
tor.enableConnectionPadding(enableConnectionPadding);
|
||||
tor.enableIpv6(ipv6Only);
|
||||
}
|
||||
enableNetwork(enableNetwork);
|
||||
tor.enableNetwork(enableNetwork);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enableConnectionPadding(boolean enable) throws IOException {
|
||||
if (!state.enableConnectionPadding(enable)) return; // Unchanged
|
||||
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
|
||||
}
|
||||
|
||||
private void enableIpv6(boolean enable) throws IOException {
|
||||
if (!state.enableIpv6(enable)) return; // Unchanged
|
||||
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
|
||||
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
protected class PluginState {
|
||||
private class PluginState {
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean started = false,
|
||||
stopped = false,
|
||||
networkInitialised = false,
|
||||
networkEnabled = false,
|
||||
paddingEnabled = false,
|
||||
ipv6Enabled = false,
|
||||
bootstrapped = false,
|
||||
circuitBuilt = false,
|
||||
settingsChecked = false;
|
||||
private boolean settingsChecked = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
private int reasonsDisabled = 0;
|
||||
@@ -1003,84 +585,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, 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;
|
||||
@@ -1093,7 +604,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
// Doesn't affect getState()
|
||||
private synchronized boolean setServerSocket(ServerSocket ss) {
|
||||
if (stopped || serverSocket != null) return false;
|
||||
if (serverSocket != null || !tor.isTorRunning()) return false;
|
||||
serverSocket = ss;
|
||||
return true;
|
||||
}
|
||||
@@ -1103,57 +614,33 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (serverSocket == ss) serverSocket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of bridge types being used and returns true if the
|
||||
* list has changed. The list is empty if bridges are disabled.
|
||||
* Doesn't affect getState().
|
||||
*/
|
||||
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
|
||||
if (types.equals(bridgeTypes)) return false; // Unchanged
|
||||
bridgeTypes = types;
|
||||
return true; // Changed
|
||||
private synchronized State getState() {
|
||||
return getState(tor.getTorState());
|
||||
}
|
||||
|
||||
private synchronized State getState() {
|
||||
if (!started || stopped || !settingsChecked) {
|
||||
private synchronized State getState(TorState torState) {
|
||||
// Treat TorState.STARTED as State.STARTING_STOPPING because it's
|
||||
// only seen during startup, before TorWrapper#enableNetwork() is
|
||||
// called for the first time. TorState.NOT_STARTED and
|
||||
// TorState.STOPPED are mapped to State.STARTING_STOPPING because
|
||||
// that's the State before we've started and after we've stopped.
|
||||
if (torState == TorState.NOT_STARTED ||
|
||||
torState == TorState.STARTING ||
|
||||
torState == TorState.STARTED ||
|
||||
torState == TorState.STOPPING ||
|
||||
torState == TorState.STOPPED ||
|
||||
!settingsChecked) {
|
||||
return STARTING_STOPPING;
|
||||
}
|
||||
if (reasonsDisabled != 0) return DISABLED;
|
||||
if (!networkInitialised) return ENABLING;
|
||||
if (!networkEnabled) return INACTIVE;
|
||||
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
|
||||
? ACTIVE : ENABLING;
|
||||
if (torState == TorState.CONNECTING) return ENABLING;
|
||||
if (torState == TorState.CONNECTED) return ACTIVE;
|
||||
// The plugin is enabled in settings but the device is offline
|
||||
return INACTIVE;
|
||||
}
|
||||
|
||||
private synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionConnected() {
|
||||
int oldConnected = orConnectionsConnected;
|
||||
orConnectionsConnected++;
|
||||
logOrConnections();
|
||||
if (oldConnected == 0) callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionClosed() {
|
||||
int oldConnected = orConnectionsConnected;
|
||||
orConnectionsConnected--;
|
||||
if (orConnectionsConnected < 0) {
|
||||
LOG.warning("Count was zero before connection closed");
|
||||
orConnectionsConnected = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void logOrConnections() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(orConnectionsConnected + " OR connections connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -16,10 +17,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;
|
||||
@@ -45,13 +46,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
protected final Executor ioExecutor, wakefulIoExecutor;
|
||||
protected final Executor ioExecutor, eventExecutor, wakefulIoExecutor;
|
||||
protected final NetworkManager networkManager;
|
||||
protected final LocationUtils locationUtils;
|
||||
protected final EventBus eventBus;
|
||||
protected final SocketFactory torSocketFactory;
|
||||
protected final BackoffFactory backoffFactory;
|
||||
protected final ResourceProvider resourceProvider;
|
||||
protected final CircumventionProvider circumventionProvider;
|
||||
protected final BatteryManager batteryManager;
|
||||
protected final Clock clock;
|
||||
@@ -61,13 +61,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||
protected final int torControlPort;
|
||||
|
||||
TorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
@@ -76,13 +76,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
this.networkManager = networkManager;
|
||||
this.locationUtils = locationUtils;
|
||||
this.eventBus = eventBus;
|
||||
this.torSocketFactory = torSocketFactory;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.resourceProvider = resourceProvider;
|
||||
this.circumventionProvider = circumventionProvider;
|
||||
this.batteryManager = batteryManager;
|
||||
this.clock = clock;
|
||||
|
||||
@@ -311,6 +311,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
if (latest == null) {
|
||||
merged = new TransportProperties(p);
|
||||
Iterator<String> it = merged.values().iterator();
|
||||
//noinspection Java8CollectionRemoveIf
|
||||
while (it.hasNext()) {
|
||||
if (isNullOrEmpty(it.next())) it.remove();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore)
|
||||
public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
|
||||
throws IOException {
|
||||
while (true) {
|
||||
if (eof()) return null;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
@@ -41,12 +41,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
class SyncRecordReaderImpl implements SyncRecordReader {
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
private static final RecordPredicate ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
private static final RecordPredicate IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
||||
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
|
||||
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
|
||||
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
|
||||
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
|
||||
n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
|
||||
n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0
|
||||
n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0
|
||||
n Bridge obfs4 109.14.168.159:5082 BFE1416DEFFE969581F016A4A319A87FFB26BA91 cert=n3X1CDdKBPXPIzfKh83p3ydfMzb0AD9gKC+/gIpHb7+xjjAnYO9x3LT+T/MvOIfAXxYySg iat-mode=0
|
||||
n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
|
||||
n Bridge obfs4 213.108.110.149:7499 519344140473CF91030B08F91521F9A6C144ED6C cert=k9fSL/d491qAkGmi2VeSwVlfuyO02jBeN54qxzzQISxpfm3b+a6kJpo8/Bfy1ACbHZIJUg iat-mode=0
|
||||
n Bridge obfs4 158.174.114.97:3456 32665CD4CBE19092CA47A53D317B8BFF5810441C cert=ne5Zt0TcMedSGmFwAs/AV6J6E9Hn7mG5mR6vQNpEfyuCZK1VRpQvU1LvvtesSu4CXqZtYQ iat-mode=0
|
||||
n Bridge obfs4 64.4.175.62:8000 8B72740D150795ACB5101AA5F95D1ACDA4FE6B3E cert=vduuNhJ5U/8hjZmllP6AFfXSlSZsnrimdR8Tm8DY9dxWS4n2j92fNc0qHihUwRqwcOfIcg iat-mode=0
|
||||
n Bridge obfs4 82.64.115.17:990 B08238781C2CD80DBD95AEABEB6F6C75F2E2CEB6 cert=1udeMlFNs3sJ20zwpPE6nShZqqwDb3F1ET4KzfSfD+fktkue9zNx9H3t+yLCPAsg+6UTUA iat-mode=1
|
||||
n Bridge obfs4 87.161.120.147:9292 9418EEBE8AEAE32CC381AF51610366E8B24651E0 cert=DFRm74qsD1i2/ypaGochpX6CS1j9JTFAKEYaHXrgrx6M2LG5Cvppdt3Ob7lULfhqgtAUdg iat-mode=0
|
||||
n Bridge obfs4 157.90.245.231:8599 C23CD468EC04555E2B37BE81A771E681049DEA6A cert=UsmDelrbwg4jc6BMvZJ0TS8klUIa2qkbRu3xwQc3ZXPEgpMqyTYUxcVwyPbIU5KmAHsmAA iat-mode=0
|
||||
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
|
||||
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
|
||||
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
|
||||
v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
|
||||
v Bridge 87.100.193.2:9010 13FB63452AADFA082BD2BC3E1E320AD301F07877
|
||||
v Bridge 65.21.240.163:33245 20BD59649212CFE7412BFC9B94C3CCCFD8F807A8
|
||||
m Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com utls=hellochrome_auto
|
||||
s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72
|
||||
@@ -1,4 +0,0 @@
|
||||
ZZ 1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
|
||||
ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
|
||||
TM 1 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
|
||||
TM 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
|
||||
@@ -0,0 +1,316 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HandshakeManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final TransactionManager db =
|
||||
context.mock(TransactionManager.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final ContactManager contactManager =
|
||||
context.mock(ContactManager.class);
|
||||
private final TransportCrypto transportCrypto =
|
||||
context.mock(TransportCrypto.class);
|
||||
private final HandshakeCrypto handshakeCrypto =
|
||||
context.mock(HandshakeCrypto.class);
|
||||
private final RecordReaderFactory recordReaderFactory =
|
||||
context.mock(RecordReaderFactory.class);
|
||||
private final RecordWriterFactory recordWriterFactory =
|
||||
context.mock(RecordWriterFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
|
||||
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||
|
||||
private final PendingContact pendingContact = getPendingContact();
|
||||
private final PublicKey theirStaticPublicKey =
|
||||
pendingContact.getPublicKey();
|
||||
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourStaticKeyPair =
|
||||
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
|
||||
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourEphemeralKeyPair =
|
||||
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
|
||||
private final SecretKey masterKey = getSecretKey();
|
||||
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
|
||||
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
|
||||
|
||||
private final InputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
private final OutputStream out = new ByteArrayOutputStream(0);
|
||||
|
||||
private final HandshakeManagerImpl handshakeManager =
|
||||
new HandshakeManagerImpl(db, identityManager, contactManager,
|
||||
transportCrypto, handshakeCrypto, recordReaderFactory,
|
||||
recordWriterFactory);
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_1(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer sends minor version, so use new key derivation
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_0(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer does not send minor version, so use old key derivation
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_0(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
|
||||
testProofOfOwnershipNotVerified(true);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
|
||||
testProofOfOwnershipNotVerified(false);
|
||||
}
|
||||
|
||||
private void testProofOfOwnershipNotVerified(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, false);
|
||||
|
||||
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
|
||||
}
|
||||
|
||||
private void expectPrepareForHandshake(boolean alice) throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(contactManager).getPendingContact(txn,
|
||||
pendingContact.getId());
|
||||
will(returnValue(pendingContact));
|
||||
oneOf(identityManager).getHandshakeKeys(txn);
|
||||
will(returnValue(ourStaticKeyPair));
|
||||
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
|
||||
ourStaticKeyPair);
|
||||
will(returnValue(alice));
|
||||
oneOf(recordReaderFactory).createRecordReader(in);
|
||||
will(returnValue(recordReader));
|
||||
oneOf(streamWriter).getOutputStream();
|
||||
will(returnValue(out));
|
||||
oneOf(recordWriterFactory).createRecordWriter(out);
|
||||
will(returnValue(recordWriter));
|
||||
oneOf(handshakeCrypto).generateEphemeralKeyPair();
|
||||
will(returnValue(ourEphemeralKeyPair));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendMinorVersion() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectReceiveMinorVersion() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectSendKey() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
ourEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectReceiveKey() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
theirEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveProof(boolean alice) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
|
||||
will(returnValue(ourProof));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendProof() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
|
||||
}
|
||||
|
||||
private void expectReceiveProof() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
|
||||
}
|
||||
|
||||
private void expectSendEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReceiveEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectVerifyOwnership(boolean alice, boolean verified) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
|
||||
theirProof);
|
||||
will(returnValue(verified));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectWriteRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
|
||||
Record.class, r -> recordEquals(record, r))));
|
||||
oneOf(recordWriter).flush();
|
||||
}});
|
||||
}
|
||||
|
||||
private boolean recordEquals(Record expected, Record actual) {
|
||||
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
|
||||
expected.getRecordType() == actual.getRecordType() &&
|
||||
Arrays.equals(expected.getPayload(), actual.getPayload());
|
||||
}
|
||||
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
@@ -174,7 +175,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
|
||||
rawLink[0] = (byte) formatVersion;
|
||||
byte[] publicKeyBytes = publicKey.getEncoded();
|
||||
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
|
||||
String base32 = Base32.encode(rawLink).toLowerCase();
|
||||
String base32 = Base32.encode(rawLink).toLowerCase(Locale.US);
|
||||
assertEquals(BASE32_LINK_BYTES, base32.length());
|
||||
return base32;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecretBadly() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
KeyPair aStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair aEphemeral = crypto.generateAgreementKeyPair();
|
||||
KeyPair bStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair bEphemeral = crypto.generateAgreementKeyPair();
|
||||
SecretKey aShared = crypto.deriveSharedSecretBadly(label,
|
||||
bStatic.getPublic(), bEphemeral.getPublic(), aStatic,
|
||||
aEphemeral, true, inputs);
|
||||
SecretKey bShared = crypto.deriveSharedSecretBadly(label,
|
||||
aStatic.getPublic(), aEphemeral.getPublic(), bStatic,
|
||||
bEphemeral, false, inputs);
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecret() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class OnionEncodingTest extends BrambleTestCase {
|
||||
|
||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||
|
||||
private final CryptoComponent crypto =
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
@Test
|
||||
public void testHostnameIsValid() {
|
||||
byte[] publicKey = new byte[32];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
secureRandom.nextBytes(publicKey);
|
||||
String onion = crypto.encodeOnion(publicKey);
|
||||
assertTrue(onion, ONION_V3.matcher(onion).matches());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
@@ -8,11 +7,13 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Test;
|
||||
@@ -21,8 +22,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
@@ -119,7 +118,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
kat.receiveKey();
|
||||
}
|
||||
@@ -148,7 +147,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup();
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
@@ -209,12 +208,22 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
assertArrayEquals(expectedPayload, actual.getPayload());
|
||||
}
|
||||
|
||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
//noinspection unchecked
|
||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
||||
with(any(Predicate.class)));
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CircumventionProviderImplTest extends BrambleTestCase {
|
||||
|
||||
private final CircumventionProviderImpl provider =
|
||||
new CircumventionProviderImpl();
|
||||
|
||||
@Test
|
||||
public void testInvariants() {
|
||||
Set<String> blocked = new HashSet<>(asList(BLOCKED));
|
||||
Set<String> bridges = new HashSet<>(asList(BRIDGES));
|
||||
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
Set<String> nonDefaultBridges =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
|
||||
// BRIDGES should be a subset of BLOCKED
|
||||
assertTrue(blocked.containsAll(bridges));
|
||||
// BRIDGES should be the union of the bridge type sets
|
||||
Set<String> union = new HashSet<>(defaultBridges);
|
||||
union.addAll(nonDefaultBridges);
|
||||
union.addAll(dpiBridges);
|
||||
assertEquals(bridges, union);
|
||||
// The bridge type sets should not overlap
|
||||
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
|
||||
assertEmptyIntersection(defaultBridges, dpiBridges);
|
||||
assertEmptyIntersection(nonDefaultBridges, dpiBridges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBestBridgeType() {
|
||||
for (String country : DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : NON_DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : DPI_BRIDGES) {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes("ZZ"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasSnowflakeParamsWithLetsEncrypt() {
|
||||
testHasSnowflakeParams(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasSnowflakeParamsWithoutLetsEncrypt() {
|
||||
testHasSnowflakeParams(false);
|
||||
}
|
||||
|
||||
private void testHasSnowflakeParams(boolean letsEncrypt) {
|
||||
String tmParams = provider.getSnowflakeParams("TM", letsEncrypt);
|
||||
String defaultParams = provider.getSnowflakeParams("ZZ", letsEncrypt);
|
||||
assertFalse(tmParams.isEmpty());
|
||||
assertFalse(defaultParams.isEmpty());
|
||||
assertNotEquals(defaultParams, tmParams);
|
||||
}
|
||||
|
||||
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
|
||||
Set<T> intersection = new HashSet<>(a);
|
||||
intersection.retainAll(b);
|
||||
assertTrue(intersection.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.record;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
@@ -128,12 +128,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
|
||||
// Accept records with version 0, type 0 or 1
|
||||
Predicate<Record> accept = r -> {
|
||||
RecordPredicate accept = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && (type == 0 || type == 1);
|
||||
};
|
||||
// Ignore records with version 0, any other type
|
||||
Predicate<Record> ignore = r -> {
|
||||
RecordPredicate ignore = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && !(type == 0 || type == 1);
|
||||
};
|
||||
@@ -183,12 +183,12 @@ public class RecordReaderImplTest extends BrambleTestCase {
|
||||
RecordReader reader = new RecordReaderImpl(in);
|
||||
|
||||
// Accept records with version 0, type 0 or 1
|
||||
Predicate<Record> accept = r -> {
|
||||
RecordPredicate accept = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && (type == 0 || type == 1);
|
||||
};
|
||||
// Ignore records with version 0, any other type
|
||||
Predicate<Record> ignore = r -> {
|
||||
RecordPredicate ignore = r -> {
|
||||
byte version = r.getProtocolVersion(), type = r.getRecordType();
|
||||
return version == 0 && !(type == 0 || type == 1);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -24,8 +24,6 @@ import org.junit.Test;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||
@@ -186,7 +184,7 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||
expectReadRecord(createAck());
|
||||
expectReadRecord(null);
|
||||
expectReadEof();
|
||||
|
||||
SyncRecordReader reader =
|
||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||
@@ -212,15 +210,25 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadRecord(@Nullable Record record) throws Exception {
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
//noinspection unchecked
|
||||
oneOf(recordReader).readRecord(with(any(Predicate.class)),
|
||||
with(any(Predicate.class)));
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReadEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
|
||||
private Record createMessage(int payloadLength) {
|
||||
return new Record(PROTOCOL_VERSION, MESSAGE, new byte[payloadLength]);
|
||||
}
|
||||
|
||||
@@ -234,12 +234,12 @@ public abstract class BrambleIntegrationTest<C extends BrambleIntegrationTestCom
|
||||
}
|
||||
|
||||
protected void awaitPendingMessageDelivery(int num)
|
||||
throws TimeoutException {
|
||||
throws TimeoutException, InterruptedException {
|
||||
awaitPendingMessageDelivery(num, TIMEOUT);
|
||||
}
|
||||
|
||||
protected void awaitPendingMessageDelivery(int num, long timeout)
|
||||
throws TimeoutException {
|
||||
throws TimeoutException, InterruptedException {
|
||||
deliveryWaiter.await(timeout, num);
|
||||
assertEquals("Messages delivered", num, deliveryCounter.getAndSet(0));
|
||||
|
||||
|
||||
@@ -25,11 +25,6 @@ public class TestFeatureFlagModule {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableMailbox() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateGroupsInCore() {
|
||||
return true;
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static java.lang.Double.compare;
|
||||
import static java.lang.Double.parseDouble;
|
||||
import static org.briarproject.bramble.test.UTest.Result.INCONCLUSIVE;
|
||||
import static org.briarproject.bramble.test.UTest.Result.LARGER;
|
||||
import static org.briarproject.bramble.test.UTest.Result.SMALLER;
|
||||
@@ -134,7 +136,7 @@ public class UTest {
|
||||
if (nA < 5 || nB < 5) die("Too few values for U test\n");
|
||||
|
||||
double zCritical;
|
||||
if (args.length == 3) zCritical = Double.valueOf(args[2]);
|
||||
if (args.length == 3) zCritical = parseDouble(args[2]);
|
||||
else zCritical = Z_CRITICAL_0_01;
|
||||
|
||||
switch (test(a, b, zCritical)) {
|
||||
@@ -161,7 +163,7 @@ public class UTest {
|
||||
BufferedReader in;
|
||||
in = new BufferedReader(new FileReader(filename));
|
||||
String s;
|
||||
while ((s = in.readLine()) != null) values.add(new Double(s));
|
||||
while ((s = in.readLine()) != null) values.add(parseDouble(s));
|
||||
in.close();
|
||||
} catch (FileNotFoundException fnf) {
|
||||
die(filename + " not found");
|
||||
@@ -187,9 +189,7 @@ public class UTest {
|
||||
|
||||
@Override
|
||||
public int compareTo(@Nonnull Value v) {
|
||||
if (value < v.value) return -1;
|
||||
if (value > v.value) return 1;
|
||||
return 0;
|
||||
return compare(value, v.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,73 @@
|
||||
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-core:2.13.4:jackson-core-2.13.4.jar:4c2e043200edd9ee7ba6fc378bd5c17784a5bf2388e152d208068b51fd0839cf',
|
||||
'com.fasterxml.jackson.core:jackson-databind:2.13.4:jackson-databind-2.13.4.jar:c9faff420d9e2c7e1e4711dbeebec2506a32c9942027211c5c293d8d87807eb6',
|
||||
'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.43.2:dagger-compiler-2.43.2.jar:298c020ee6ed2f4cc651ebbfdb7f8de329b07c44b618d65be117846a850e2a03',
|
||||
'com.google.dagger:dagger-producers:2.43.2:dagger-producers-2.43.2.jar:e7f5d9ffc85d48a49c8e22e02833d418f7ccad5d7512f529964db5127ab915ff',
|
||||
'com.google.dagger:dagger-spi:2.43.2:dagger-spi-2.43.2.jar:3bae8d9dadeaaa5927da6f094389a560c12c05fec3d2711d2fa79292c7a7d7ad',
|
||||
'com.google.dagger:dagger:2.43.2:dagger-2.43.2.jar:c89681f7cbbf8c527bf4ac2748515d617fdb54a1d425c08d914fdc28192b5fe4',
|
||||
'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.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: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.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.squareup.okhttp3:mockwebserver:4.9.3:mockwebserver-4.9.3.jar:9c8c581c29f22f877a35d11380462f75bb24bf1886204fe835ee695594a2784e',
|
||||
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
|
||||
'com.squareup.okhttp3:okhttp:4.9.3:okhttp-4.9.3.jar:93ecd6cba19d87dccfe566ec848d91aae799e3cf16c00709358ea69bd9227219',
|
||||
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
|
||||
'com.squareup.okio:okio:2.8.0:okio-jvm-2.8.0.jar:4496b06e73982fcdd8a5393f46e5df2ce2fa4465df5895454cac68a32f09bbc8',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'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',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.jodah:concurrentunit:0.4.2:concurrentunit-0.4.2.jar:5583078e1acf91734939e985bc9e7ee947b0e93a8eef679da6bb07bbeb47ced3',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886',
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:socks-socket:0.1:socks-socket-0.1.jar:e5898822d10f5390363c5dddb945891648c92cf93ba50709e07f0d173ec0eb4b',
|
||||
'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.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'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',
|
||||
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10:kotlin-stdlib-common-1.4.10.jar:4681f2d436a68c7523595d84ed5758e1382f9da0f67c91e6a848690d711274fe',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10:kotlin-stdlib-jdk7-1.4.10.jar:f9566380c08722c780ce33ceee23e98ddf765ca98fabd3e2fabae7975c8d232b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10:kotlin-stdlib-jdk8-1.4.10.jar:39b7a9442d7a3865e0f4a732c56c1d5da0e11ffb3bb82a461d32deb0c0ca7673',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.10:kotlin-stdlib-1.4.10.jar:01ecb09782c042b931c1839acf21a188340b295d05400afd6e3415d4475b8daa',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
|
||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||
'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.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.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
|
||||
]
|
||||
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-core:2.13.4:jackson-core-2.13.4.jar:4c2e043200edd9ee7ba6fc378bd5c17784a5bf2388e152d208068b51fd0839cf',
|
||||
'com.fasterxml.jackson.core:jackson-databind:2.13.4:jackson-databind-2.13.4.jar:c9faff420d9e2c7e1e4711dbeebec2506a32c9942027211c5c293d8d87807eb6',
|
||||
'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.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: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.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.squareup.okhttp3:mockwebserver:4.10.0:mockwebserver-4.10.0.jar:af29da234e63159d6e0dea43bf8288eea97d71cdf1651a5ee2d6c0d0d4adbf8f',
|
||||
'com.squareup.okhttp3:okhttp:4.10.0:okhttp-4.10.0.jar:7580f14fa1691206e37081ad3f92063b1603b328da0bb316f2fef02e0562e7ec',
|
||||
'com.squareup.okio:okio-jvm:3.0.0:okio-jvm-3.0.0.jar:be64a0cc1f28ea9cd5c970dd7e7557af72c808d738c495b397bf897c9921e907',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
|
||||
'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',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.jodah:concurrentunit:0.4.6:concurrentunit-0.4.6.jar:760e6d4ab7801484de09da621b61141f3b2c2432949da9eb13f076e5d9a5e0a5',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.bouncycastle:bcprov-jdk15to18:1.71:bcprov-jdk15to18-1.71.jar:143aaa4a40edd5fc2a18db7900059f6c16f4d931b94b94b20f7e2238e6662886',
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:onionwrapper-core:0.1.0:onionwrapper-core-0.1.0.jar:3e6631771b891c959403f6145de034c6f9816e7d067808d534f954eef9a1ca35',
|
||||
'org.briarproject:socks-socket:0.1:socks-socket-0.1.jar:e5898822d10f5390363c5dddb945891648c92cf93ba50709e07f0d173ec0eb4b',
|
||||
'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.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.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.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
||||
'org.jetbrains.kotlin:kotlin-reflect:1.6.10:kotlin-reflect-1.6.10.jar:3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20:kotlin-stdlib-common-1.6.20.jar:8da40a2520d30dcb1012176fe93d24e82d08a3e346c37e0343b0fb6f64f6be01',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31:kotlin-stdlib-jdk7-1.5.31.jar:a25bf47353ce899d843cbddee516d621a73473e7fba97f8d0301e7b4aed7c15f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10:kotlin-stdlib-jdk7-1.6.10.jar:2aedcdc6b69b33bdf5cc235bcea88e7cf6601146bb6bcdffdb312bbacd7be261',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31:kotlin-stdlib-jdk8-1.5.31.jar:b548f7767aacf029d2417e47440742bd6d3ebede19b60386e23554ce5c4c5fdc',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10:kotlin-stdlib-jdk8-1.6.10.jar:1456d82d039ea30d8485b032901f52bbf07e7cdbe8bb1f8708ad32a8574c41ce',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.6.20:kotlin-stdlib-1.6.20.jar:eeb51c2b67b26233fd81d0bc4f8044ec849718890905763ceffd84a31e2cb799',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
|
||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||
'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.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.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,13 +11,10 @@ dependencies {
|
||||
implementation project(':bramble-core')
|
||||
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
def jna_version = '4.5.2'
|
||||
def jna_version = '5.13.0'
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
|
||||
testImplementation "org.briarproject:tor-linux:$tor_version"
|
||||
testImplementation "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
|
||||
testImplementation "org.briarproject:snowflake-linux:$snowflake_version"
|
||||
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -27,9 +24,6 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.network.JavaNetworkModule;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface BrambleJavaEagerSingletons {
|
||||
|
||||
void inject(JavaNetworkModule.EagerSingletons init);
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(BrambleJavaEagerSingletons c) {
|
||||
c.inject(new JavaNetworkModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,51 @@
|
||||
package org.briarproject.bramble.network;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.network.NetworkStatus;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class JavaNetworkManager implements NetworkManager {
|
||||
@NotNullByDefault
|
||||
class JavaNetworkManager implements NetworkManager, Service {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(JavaNetworkManager.class.getName());
|
||||
|
||||
private final TaskScheduler scheduler;
|
||||
private final Executor ioExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final AtomicReference<NetworkStatus> lastStatus =
|
||||
new AtomicReference<>();
|
||||
|
||||
@Inject
|
||||
JavaNetworkManager() {
|
||||
JavaNetworkManager(TaskScheduler scheduler,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
EventBus eventBus) {
|
||||
this.scheduler = scheduler;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,7 +66,29 @@ class JavaNetworkManager implements NetworkManager {
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Connected: " + connected
|
||||
+ ", has IPv4 address: " + hasIpv4
|
||||
+ ", has IPv6 unicast address: " + hasIpv6Unicast);
|
||||
}
|
||||
return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
|
||||
}
|
||||
|
||||
private void broadcastNetworkStatusIfChanged() {
|
||||
NetworkStatus status = getNetworkStatus();
|
||||
NetworkStatus old = lastStatus.getAndSet(status);
|
||||
if (!status.equals(old)) {
|
||||
eventBus.broadcast(new NetworkStatusEvent(status));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() {
|
||||
scheduler.scheduleWithFixedDelay(this::broadcastNetworkStatusIfChanged,
|
||||
ioExecutor, 0, 10, SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.bramble.network;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -10,9 +12,16 @@ import dagger.Provides;
|
||||
@Module
|
||||
public class JavaNetworkModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
NetworkManager networkManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
NetworkManager provideNetworkManager(JavaNetworkManager networkManager) {
|
||||
NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,
|
||||
JavaNetworkManager networkManager) {
|
||||
lifecycleManager.registerService(networkManager);
|
||||
return networkManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.CodeSource;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@NotNullByDefault
|
||||
abstract class JavaTorPlugin extends TorPlugin {
|
||||
|
||||
JavaTorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, callback, architecture,
|
||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getLastUpdateTime() {
|
||||
CodeSource codeSource =
|
||||
getClass().getProtectionDomain().getCodeSource();
|
||||
if (codeSource == null) throw new AssertionError("CodeSource null");
|
||||
try {
|
||||
URI path = codeSource.getLocation().toURI();
|
||||
File file = new File(path);
|
||||
return file.lastModified();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
import org.briarproject.onionwrapper.LocationUtils;
|
||||
import org.briarproject.onionwrapper.MacTorWrapper;
|
||||
import org.briarproject.onionwrapper.TorWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.util.OsUtils.isMac;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MacTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
MacTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
CryptoComponent crypto,
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getArchitectureForTorBinary() {
|
||||
if (!isMac()) return null;
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
if (arch.equals("x86_64")) return "x86_64";
|
||||
else if (arch.equals("aarch64")) return "aarch64";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
TorWrapper tor = new MacTorWrapper(ioExecutor, eventExecutor,
|
||||
architecture, torDirectory, torSocksPort, torControlPort);
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, torSocketFactory, circumventionProvider,
|
||||
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@NotNullByDefault
|
||||
class UnixTorPlugin extends JavaTorPlugin {
|
||||
|
||||
UnixTorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, callback, architecture,
|
||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getProcessId() {
|
||||
return CLibrary.INSTANCE.getpid();
|
||||
}
|
||||
|
||||
private interface CLibrary extends Library {
|
||||
|
||||
CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
|
||||
|
||||
int getpid();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -12,10 +13,12 @@ import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
import org.briarproject.onionwrapper.LocationUtils;
|
||||
import org.briarproject.onionwrapper.TorWrapper;
|
||||
import org.briarproject.onionwrapper.UnixTorWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -34,13 +37,13 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
@@ -48,8 +51,8 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
@@ -62,6 +65,7 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
//noinspection IfCanBeSwitch
|
||||
if (arch.equals("amd64")) return "x86_64";
|
||||
else if (arch.equals("aarch64")) return "aarch64";
|
||||
else if (arch.equals("arm")) return "armhf";
|
||||
@@ -72,11 +76,11 @@ public class UnixTorPluginFactory extends TorPluginFactory {
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory, clock,
|
||||
resourceProvider, circumventionProvider, batteryManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
TorWrapper tor = new UnixTorWrapper(ioExecutor, eventExecutor,
|
||||
architecture, torDirectory, torSocksPort, torControlPort);
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, torSocketFactory, circumventionProvider,
|
||||
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
class WindowsTorPlugin extends JavaTorPlugin {
|
||||
|
||||
WindowsTorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, callback, architecture,
|
||||
maxLatency, maxIdleTime, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getProcessId() {
|
||||
return Kernel32.INSTANCE.GetCurrentProcessId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
|
||||
// Wait for the control port to be opened, then continue to read its
|
||||
// stdout and stderr in a background thread until it exits.
|
||||
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
|
||||
ioExecutor.execute(() -> {
|
||||
boolean started = false;
|
||||
// Read the process's stdout (and redirected stderr)
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Startup has succeeded when the control port is open
|
||||
while (stdout.hasNextLine()) {
|
||||
String line = stdout.nextLine();
|
||||
if (!started && line.contains("Opened Control listener")) {
|
||||
success.add(true);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
// If the control port wasn't opened, startup has failed
|
||||
if (!started) success.add(false);
|
||||
// Wait for the process to exit
|
||||
try {
|
||||
int exit = torProcess.waitFor();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Tor exited with value " + exit);
|
||||
} catch (InterruptedException e1) {
|
||||
LOG.warning("Interrupted while waiting for Tor to exit");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
// Wait for the startup result
|
||||
if (!success.take()) throw new PluginException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExecutableExtension() {
|
||||
return ".exe";
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -12,10 +13,12 @@ import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
import org.briarproject.onionwrapper.LocationUtils;
|
||||
import org.briarproject.onionwrapper.TorWrapper;
|
||||
import org.briarproject.onionwrapper.WindowsTorWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -34,13 +37,13 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
@@ -48,8 +51,8 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
@@ -70,11 +73,11 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory, clock,
|
||||
resourceProvider, circumventionProvider, batteryManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
TorWrapper tor = new WindowsTorWrapper(ioExecutor, eventExecutor,
|
||||
architecture, torDirectory, torSocksPort, torControlPort);
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, torSocketFactory, circumventionProvider,
|
||||
batteryManager, backoff, torRendezvousCrypto, tor, callback,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class DesktopSecureRandomModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
SecureRandomProvider provideSecureRandomProvider() {
|
||||
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
|
||||
if (isLinux()) return new UnixSecureRandomProvider();
|
||||
return () -> null; // Use system default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
class JavaLocationUtils implements LocationUtils {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JavaLocationUtils.class.getName());
|
||||
|
||||
@Inject
|
||||
JavaLocationUtils() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentCountry() {
|
||||
LOG.info("Using user-defined locale");
|
||||
return Locale.getDefault().getCountry();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.onionwrapper.JavaLocationUtilsFactory;
|
||||
import org.briarproject.onionwrapper.LocationUtils;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -13,8 +14,8 @@ public class JavaSystemModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LocationUtils provideLocationUtils(JavaLocationUtils locationUtils) {
|
||||
return locationUtils;
|
||||
LocationUtils provideLocationUtils() {
|
||||
return JavaLocationUtilsFactory.createJavaLocationUtils();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class BridgeTest extends BrambleTestCase {
|
||||
|
||||
private static final String[] SNOWFLAKE_COUNTRY_CODES = {"TM", "ZZ"};
|
||||
|
||||
@Parameters
|
||||
public static Iterable<Params> data() {
|
||||
BrambleJavaIntegrationTestComponent component =
|
||||
DaggerBrambleJavaIntegrationTestComponent.builder().build();
|
||||
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||
.injectEagerSingletons(component);
|
||||
// Share stats among all the test instances
|
||||
Stats stats = new Stats();
|
||||
CircumventionProvider provider = component.getCircumventionProvider();
|
||||
List<Params> states = new ArrayList<>();
|
||||
for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
|
||||
for (String bridge :
|
||||
provider.getBridges(DEFAULT_OBFS4, "", true)) {
|
||||
states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
|
||||
}
|
||||
for (String bridge :
|
||||
provider.getBridges(NON_DEFAULT_OBFS4, "", true)) {
|
||||
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats,
|
||||
false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(VANILLA, "", true)) {
|
||||
states.add(new Params(bridge, VANILLA, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(MEEK, "", true)) {
|
||||
states.add(new Params(bridge, MEEK, stats, true));
|
||||
}
|
||||
for (String countryCode : SNOWFLAKE_COUNTRY_CODES) {
|
||||
for (String bridge :
|
||||
provider.getBridges(SNOWFLAKE, countryCode, true)) {
|
||||
states.add(new Params(bridge, SNOWFLAKE, stats, true));
|
||||
}
|
||||
for (String bridge :
|
||||
provider.getBridges(SNOWFLAKE, countryCode, false)) {
|
||||
states.add(new Params(bridge, SNOWFLAKE, stats, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private final static long TIMEOUT = MINUTES.toMillis(2);
|
||||
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
|
||||
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
|
||||
private final static int ATTEMPTS_PER_BRIDGE = 5;
|
||||
|
||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||
|
||||
@Inject
|
||||
@IoExecutor
|
||||
Executor ioExecutor;
|
||||
@Inject
|
||||
@WakefulIoExecutor
|
||||
Executor wakefulIoExecutor;
|
||||
@Inject
|
||||
NetworkManager networkManager;
|
||||
@Inject
|
||||
ResourceProvider resourceProvider;
|
||||
@Inject
|
||||
BatteryManager batteryManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
@Inject
|
||||
BackoffFactory backoffFactory;
|
||||
@Inject
|
||||
Clock clock;
|
||||
@Inject
|
||||
CryptoComponent crypto;
|
||||
@Inject
|
||||
@TorSocksPort
|
||||
int torSocksPort;
|
||||
@Inject
|
||||
@TorControlPort
|
||||
int torControlPort;
|
||||
|
||||
private final File torDir = getTestDirectory();
|
||||
private final Params params;
|
||||
|
||||
private UnixTorPluginFactory factory;
|
||||
|
||||
public BridgeTest(Params params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Skip this test unless it's explicitly enabled in the environment
|
||||
assumeTrue(isOptionalTestEnabled(BridgeTest.class));
|
||||
|
||||
// TODO: Remove this assumption when the plugin supports other platforms
|
||||
assumeTrue(OsUtils.isLinux());
|
||||
|
||||
BrambleJavaIntegrationTestComponent component =
|
||||
DaggerBrambleJavaIntegrationTestComponent.builder().build();
|
||||
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||
.injectEagerSingletons(component);
|
||||
component.inject(this);
|
||||
|
||||
LocationUtils locationUtils = () -> "US";
|
||||
SocketFactory torSocketFactory = SocketFactory.getDefault();
|
||||
|
||||
@NotNullByDefault
|
||||
CircumventionProvider bridgeProvider = new CircumventionProvider() {
|
||||
@Override
|
||||
public boolean isTorProbablyBlocked(String countryCode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBridgesWork(String countryCode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||
return singletonList(params.bridgeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getBridges(BridgeType bridgeType,
|
||||
String countryCode, boolean letsEncrypt) {
|
||||
return singletonList(params.bridge);
|
||||
}
|
||||
};
|
||||
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, eventBus, torSocketFactory,
|
||||
backoffFactory, resourceProvider, bridgeProvider,
|
||||
batteryManager, clock, crypto, torDir, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(torDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBridges() throws Exception {
|
||||
if (params.stats.hasSucceeded(params.bridge)) {
|
||||
LOG.info("Skipping previously successful bridge: " + params.bridge);
|
||||
return;
|
||||
}
|
||||
|
||||
DuplexPlugin duplexPlugin =
|
||||
factory.createPlugin(new TestPluginCallback());
|
||||
assertNotNull(duplexPlugin);
|
||||
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
||||
|
||||
LOG.warning("Testing " + params.bridge);
|
||||
try {
|
||||
plugin.start();
|
||||
long start = clock.currentTimeMillis();
|
||||
long timeout = params.bridgeType == MEEK ? MEEK_TIMEOUT : TIMEOUT;
|
||||
while (clock.currentTimeMillis() - start < timeout) {
|
||||
if (plugin.getState() == ACTIVE) break;
|
||||
clock.sleep(500);
|
||||
}
|
||||
if (plugin.getState() == ACTIVE) {
|
||||
LOG.info("Connected to Tor: " + params.bridge);
|
||||
params.stats.countSuccess(params.bridge);
|
||||
} else {
|
||||
LOG.warning("Could not connect to Tor within timeout: "
|
||||
+ params.bridge);
|
||||
params.stats.countFailure(params.bridge, params.essential);
|
||||
}
|
||||
} finally {
|
||||
plugin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Params {
|
||||
|
||||
private final String bridge;
|
||||
private final BridgeType bridgeType;
|
||||
private final Stats stats;
|
||||
private final boolean essential;
|
||||
|
||||
private Params(String bridge, BridgeType bridgeType,
|
||||
Stats stats, boolean essential) {
|
||||
this.bridge = bridge;
|
||||
this.bridgeType = bridgeType;
|
||||
this.stats = stats;
|
||||
this.essential = essential;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Stats {
|
||||
|
||||
@GuardedBy("this")
|
||||
private final Set<String> successes = new HashSet<>();
|
||||
@GuardedBy("this")
|
||||
private final Multiset<String> failures = new Multiset<>();
|
||||
@GuardedBy("this")
|
||||
private final Set<String> unreachable = new TreeSet<>();
|
||||
|
||||
private synchronized boolean hasSucceeded(String bridge) {
|
||||
return successes.contains(bridge);
|
||||
}
|
||||
|
||||
private synchronized void countSuccess(String bridge) {
|
||||
successes.add(bridge);
|
||||
}
|
||||
|
||||
private synchronized void countFailure(String bridge,
|
||||
boolean essential) {
|
||||
if (failures.add(bridge) == ATTEMPTS_PER_BRIDGE) {
|
||||
LOG.warning("Bridge is unreachable after "
|
||||
+ ATTEMPTS_PER_BRIDGE + " attempts: " + bridge);
|
||||
unreachable.add(bridge);
|
||||
if (unreachable.size() > UNREACHABLE_BRIDGES_ALLOWED) {
|
||||
fail(unreachable.size() + " bridges are unreachable: "
|
||||
+ unreachable);
|
||||
}
|
||||
if (essential) {
|
||||
fail("essential bridge is unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.Plugin.State;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestPluginCallback implements PluginCallback {
|
||||
|
||||
@Override
|
||||
public Settings getSettings() {
|
||||
return new Settings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getLocalProperties() {
|
||||
return new TransportProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TransportProperties> getRemoteProperties() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeSettings(Settings s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeLocalProperties(TransportProperties p) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pluginStateChanged(State state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConnection(DuplexTransportConnection c) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReader(TransportConnectionReader r) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleWriter(TransportConnectionWriter w) {
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.BrambleJavaModule;
|
||||
import org.briarproject.bramble.mailbox.ModularMailboxModule;
|
||||
import org.briarproject.bramble.plugin.tor.BridgeTest;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
BrambleCoreIntegrationTestModule.class,
|
||||
BrambleCoreModule.class,
|
||||
BrambleJavaModule.class,
|
||||
ModularMailboxModule.class,
|
||||
TestTorPortsModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
})
|
||||
public interface BrambleJavaIntegrationTestComponent
|
||||
extends BrambleCoreIntegrationTestEagerSingletons {
|
||||
|
||||
void inject(BridgeTest init);
|
||||
|
||||
CircumventionProvider getCircumventionProvider();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class TestResources {
|
||||
|
||||
@Before
|
||||
public void isRunningOnLinux() {
|
||||
assumeTrue(OsUtils.isLinux());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canReadTorLinux() {
|
||||
InputStream input = Thread.currentThread().getContextClassLoader()
|
||||
.getResourceAsStream("x86_64/tor");
|
||||
assertNotNull(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canReadObfs4ProxyLinux() {
|
||||
InputStream input = Thread.currentThread().getContextClassLoader()
|
||||
.getResourceAsStream("x86_64/obfs4proxy");
|
||||
assertNotNull(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canReadSnowflakeLinux() {
|
||||
InputStream input = Thread.currentThread().getContextClassLoader()
|
||||
.getResourceAsStream("x86_64/snowflake");
|
||||
assertNotNull(input);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user