mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
22 Commits
macos
...
beta-1.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
624f11a61f | ||
|
|
c6a284bd6d | ||
|
|
9d4d992009 | ||
|
|
e92eb1c699 | ||
|
|
07e56f7086 | ||
|
|
fe31e60e66 | ||
|
|
7810e7e848 | ||
|
|
2566105f13 | ||
|
|
cab8f834bd | ||
|
|
ec0a754289 | ||
|
|
e81fe44ea1 | ||
|
|
e399b9196a | ||
|
|
aadbd3a662 | ||
|
|
f4fd65aee4 | ||
|
|
61e7d2ebf9 | ||
|
|
06dd8c65aa | ||
|
|
2f351b318e | ||
|
|
a468af94db | ||
|
|
49f10e7e82 | ||
|
|
01b1741e83 | ||
|
|
b7003a3587 | ||
|
|
f580525734 |
@@ -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/**/*
|
||||
|
||||
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>
|
||||
@@ -13,8 +13,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
versionCode 10423
|
||||
versionName "1.4.23"
|
||||
versionCode 10501
|
||||
versionName "1.5.1"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -40,6 +40,8 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'org.briarproject:dont-kill-me-lib:0.2.6'
|
||||
|
||||
// In theory this dependency shouldn't be needed, but without it Android Studio's linter will
|
||||
// complain about unresolved symbols for bramble-api test classes in bramble-android tests,
|
||||
// even though the bramble-api test classes are provided by the testImplementation dependency
|
||||
@@ -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,10 @@
|
||||
|
||||
<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 -->
|
||||
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 and Nubia devices running API 32 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="31" />
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.briarproject.android.dontkillmelib.wakelock;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class AndroidWakeLockModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
AndroidWakeLockManager provideWakeLockManager(
|
||||
AndroidWakeLockManagerImpl wakeLockManager) {
|
||||
return wakeLockManager;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockModule;
|
||||
import org.briarproject.bramble.battery.AndroidBatteryModule;
|
||||
import org.briarproject.bramble.io.DnsModule;
|
||||
import org.briarproject.bramble.network.AndroidNetworkModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
||||
import org.briarproject.bramble.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.AndroidWakefulIoExecutorModule;
|
||||
import org.briarproject.bramble.system.DefaultThreadFactoryModule;
|
||||
import org.briarproject.onionwrapper.CircumventionModule;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
@@ -19,6 +20,7 @@ import dagger.Module;
|
||||
AndroidSystemModule.class,
|
||||
AndroidTaskSchedulerModule.class,
|
||||
AndroidWakefulIoExecutorModule.class,
|
||||
AndroidWakeLockModule.class,
|
||||
DefaultThreadFactoryModule.class,
|
||||
CircumventionModule.class,
|
||||
DnsModule.class,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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.TorWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -28,6 +31,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
|
||||
|
||||
@Immutable
|
||||
@@ -39,13 +43,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
@@ -55,8 +59,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
@TorControlPort int torControlPort,
|
||||
Application app,
|
||||
AndroidWakeLockManager wakeLockManager) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
|
||||
locationUtils, eventBus, torSocketFactory, backoffFactory,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
this.app = app;
|
||||
@@ -79,12 +83,18 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new AndroidTorPlugin(ioExecutor,
|
||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, wakeLockManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
|
||||
ioExecutor, eventExecutor, architecture, torDirectory,
|
||||
torSocksPort, torControlPort);
|
||||
// Android versions 7.1 and newer can verify Let's Encrypt TLS certs
|
||||
// signed with the IdentTrust DST Root X3 certificate. Older versions
|
||||
// of Android consider the certificate to have expired at the end of
|
||||
// September 2021.
|
||||
boolean canVerifyLetsEncryptCerts = SDK_INT >= 25;
|
||||
return new TorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
torRendezvousCrypto, tor, callback, MAX_LATENCY,
|
||||
MAX_IDLE_TIME, canVerifyLetsEncryptCerts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.system;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
@@ -69,11 +68,4 @@ public class AndroidSystemModule {
|
||||
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
AndroidWakeLockManager provideWakeLockManager(
|
||||
AndroidWakeLockManagerImpl wakeLockManager) {
|
||||
return wakeLockManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -24,7 +24,12 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.briarproject:dont-kill-me-lib:0.2.6:dont-kill-me-lib-0.2.6.aar:8a4cc201143227c0865c2edfba035f71109bf02e1ab26444fa3e42d3c569960f',
|
||||
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
|
||||
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
|
||||
'org.briarproject:onionwrapper-android:0.0.1:onionwrapper-android-0.0.1.aar:959115946586daa090f057645cf75992407a59025e221c3bf88d2aa930ef3919',
|
||||
'org.briarproject:onionwrapper-core:0.0.1:onionwrapper-core-0.0.1.jar:a1937506b00ee6620e909a500e5d004be81f94a6f7d7c898e1a9e841a8ae8a2a',
|
||||
'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',
|
||||
@@ -37,11 +42,12 @@ dependencyVerification {
|
||||
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c',
|
||||
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0:kotlin-stdlib-jdk7-1.8.0.jar:4c889d1d9803f5f2eb6c1592a6b7e62369ac7660c9eee15aba16fec059163666',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
|
||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||
|
||||
@@ -11,8 +11,6 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableMailbox();
|
||||
|
||||
boolean shouldEnablePrivateGroupsInCore();
|
||||
|
||||
boolean shouldEnableForumsInCore();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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.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;
|
||||
@@ -91,36 +77,19 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
import static org.briarproject.bramble.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 +98,79 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final NetworkManager networkManager;
|
||||
private final LocationUtils locationUtils;
|
||||
private final SocketFactory torSocketFactory;
|
||||
private final Clock clock;
|
||||
private final CircumventionProvider circumventionProvider;
|
||||
private final BatteryManager batteryManager;
|
||||
private final Backoff backoff;
|
||||
private final TorRendezvousCrypto torRendezvousCrypto;
|
||||
private final TorWrapper tor;
|
||||
private final PluginCallback callback;
|
||||
private final String architecture;
|
||||
private final CircumventionProvider circumventionProvider;
|
||||
private final ResourceProvider resourceProvider;
|
||||
private final long maxLatency;
|
||||
private final int maxIdleTime;
|
||||
private final boolean canVerifyLetsEncryptCerts;
|
||||
private final int socketTimeout;
|
||||
private final File torDirectory;
|
||||
private final File configFile;
|
||||
private final int torSocksPort;
|
||||
private final int torControlPort;
|
||||
private final File doneFile, cookieFile;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
protected final PluginState state = new PluginState();
|
||||
|
||||
private volatile Socket controlSocket = null;
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile Settings settings = null;
|
||||
|
||||
protected abstract int getProcessId();
|
||||
|
||||
protected abstract long getLastUpdateTime();
|
||||
|
||||
TorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
SocketFactory torSocketFactory,
|
||||
Clock clock,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
TorWrapper tor,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
long maxLatency,
|
||||
int maxIdleTime,
|
||||
File torDirectory,
|
||||
int torSocksPort,
|
||||
int torControlPort) {
|
||||
boolean canVerifyLetsEncryptCerts) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
this.networkManager = networkManager;
|
||||
this.locationUtils = locationUtils;
|
||||
this.torSocketFactory = torSocketFactory;
|
||||
this.clock = clock;
|
||||
this.resourceProvider = resourceProvider;
|
||||
this.circumventionProvider = circumventionProvider;
|
||||
this.batteryManager = batteryManager;
|
||||
this.backoff = backoff;
|
||||
this.torRendezvousCrypto = torRendezvousCrypto;
|
||||
this.tor = tor;
|
||||
this.callback = callback;
|
||||
this.architecture = architecture;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||
this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2) {
|
||||
socketTimeout = Integer.MAX_VALUE;
|
||||
else socketTimeout = maxIdleTime * 2;
|
||||
this.torDirectory = torDirectory;
|
||||
this.torSocksPort = torSocksPort;
|
||||
this.torControlPort = torControlPort;
|
||||
configFile = new File(torDirectory, "torrc");
|
||||
doneFile = new File(torDirectory, "done");
|
||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||
} else {
|
||||
socketTimeout = maxIdleTime * 2;
|
||||
}
|
||||
// Don't execute more than one connection status check at a time
|
||||
connectionStatusExecutor =
|
||||
new PoliteExecutor("TorPlugin", ioExecutor, 1);
|
||||
}
|
||||
tor.setObserver(new Observer() {
|
||||
|
||||
protected File getTorExecutableFile() {
|
||||
return new File(torDirectory, "tor");
|
||||
}
|
||||
@Override
|
||||
public void onState(TorState torState) {
|
||||
State s = state.getState(torState);
|
||||
if (s == ACTIVE) backoff.reset();
|
||||
callback.pluginStateChanged(s);
|
||||
}
|
||||
|
||||
protected File getObfs4ExecutableFile() {
|
||||
return new File(torDirectory, "obfs4proxy");
|
||||
}
|
||||
@Override
|
||||
public void onBootstrapPercentage(int percentage) {
|
||||
}
|
||||
|
||||
protected File getSnowflakeExecutableFile() {
|
||||
return new File(torDirectory, "snowflake");
|
||||
@Override
|
||||
public void onHsDescriptorUpload(String onion) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClockSkewDetected(long skewSeconds) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,89 +191,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 +210,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 +233,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 +245,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 +289,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void enableNetwork(boolean enable) throws IOException {
|
||||
if (!state.enableNetwork(enable)) return; // Unchanged
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
}
|
||||
|
||||
private void enableBridges(List<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, canVerifyLetsEncryptCerts));
|
||||
}
|
||||
controlConnection.setConf(conf);
|
||||
tor.enableBridges(bridges);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this device can verify Let's Encrypt certificates signed
|
||||
* with the IdentTrust DST Root X3 certificate, which expired at the end of
|
||||
* September 2021.
|
||||
*/
|
||||
protected boolean canVerifyLetsEncryptCerts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ServerSocket ss = state.setStopped();
|
||||
tryToClose(ss, LOG, WARNING);
|
||||
if (controlSocket != null && controlConnection != null) {
|
||||
try {
|
||||
LOG.info("Stopping Tor");
|
||||
controlConnection.shutdownTor("TERM");
|
||||
controlSocket.close();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
try {
|
||||
tor.stop();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,6 +421,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 +438,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 +448,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 +461,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,7 +483,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private void updateConnectionStatus(NetworkStatus status,
|
||||
boolean charging) {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
if (!state.isTorRunning()) return;
|
||||
if (!tor.isTorRunning()) return;
|
||||
boolean online = status.isConnected();
|
||||
boolean wifi = status.isWifi();
|
||||
boolean ipv6Only = status.isIpv6Only();
|
||||
@@ -960,41 +567,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 +591,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 +610,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 +620,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (serverSocket == ss) serverSocket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of bridge types being used and returns true if the
|
||||
* list has changed. The list is empty if bridges are disabled.
|
||||
* Doesn't affect getState().
|
||||
*/
|
||||
private synchronized boolean setBridgeTypes(List<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) {
|
||||
if (torState == TorState.STARTING_STOPPING || !settingsChecked) {
|
||||
return STARTING_STOPPING;
|
||||
}
|
||||
if (reasonsDisabled != 0) return DISABLED;
|
||||
if (!networkInitialised) return ENABLING;
|
||||
if (!networkEnabled) return INACTIVE;
|
||||
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
|
||||
? ACTIVE : ENABLING;
|
||||
if (torState == TorState.CONNECTING) return ENABLING;
|
||||
if (torState == TorState.CONNECTED) return ACTIVE;
|
||||
return INACTIVE;
|
||||
}
|
||||
|
||||
private synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionConnected() {
|
||||
int oldConnected = orConnectionsConnected;
|
||||
orConnectionsConnected++;
|
||||
logOrConnections();
|
||||
if (oldConnected == 0) callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionClosed() {
|
||||
int oldConnected = orConnectionsConnected;
|
||||
orConnectionsConnected--;
|
||||
if (orConnectionsConnected < 0) {
|
||||
LOG.warning("Count was zero before connection closed");
|
||||
orConnectionsConnected = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void logOrConnections() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(orConnectionsConnected + " OR connections connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -17,9 +18,9 @@ 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 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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
package org.briarproject.onionwrapper;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,6 @@ public class TestFeatureFlagModule {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableMailbox() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateGroupsInCore() {
|
||||
return true;
|
||||
|
||||
@@ -37,6 +37,7 @@ dependencyVerification {
|
||||
'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.0.1:onionwrapper-core-0.0.1.jar:a1937506b00ee6620e909a500e5d004be81f94a6f7d7c898e1a9e841a8ae8a2a',
|
||||
'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',
|
||||
|
||||
@@ -14,10 +14,7 @@ dependencies {
|
||||
def jna_version = '4.5.2'
|
||||
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) {
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.briarproject.bramble;
|
||||
import org.briarproject.bramble.io.DnsModule;
|
||||
import org.briarproject.bramble.mailbox.ModularMailboxModule;
|
||||
import org.briarproject.bramble.network.JavaNetworkModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
||||
import org.briarproject.bramble.socks.SocksModule;
|
||||
import org.briarproject.bramble.system.JavaSystemModule;
|
||||
import org.briarproject.onionwrapper.CircumventionModule;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -13,9 +14,11 @@ 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.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, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -13,9 +14,11 @@ 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.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, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
|
||||
|
||||
@Module
|
||||
class TestTorPortsModule {
|
||||
|
||||
@Provides
|
||||
@TorSocksPort
|
||||
int provideTorSocksPort() {
|
||||
return DEFAULT_SOCKS_PORT + 10;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@TorControlPort
|
||||
int provideTorControlPort() {
|
||||
return DEFAULT_CONTROL_PORT + 10;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ dependencyVerification {
|
||||
'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.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
|
||||
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
|
||||
'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',
|
||||
@@ -26,9 +24,10 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.briarproject:obfs4proxy-linux:0.0.14-tor2:obfs4proxy-linux-0.0.14-tor2.jar:bb2431092b5ad998ad620b0223e725c0f7e43f1b02af2f097a2544edc1fd9738',
|
||||
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
|
||||
'org.briarproject:tor-linux:0.4.7.13-2:tor-linux-0.4.7.13-2.jar:1e4ca9e0f724e1f17fcce570832704942cc3be26c4c2eccbe5aae29f35afa307',
|
||||
'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.0.1:onionwrapper-core-0.0.1.jar:a1937506b00ee6620e909a500e5d004be81f94a6f7d7c898e1a9e841a8ae8a2a',
|
||||
'org.briarproject:onionwrapper-java:0.0.1:onionwrapper-java-0.0.1.jar:102ccea934d02b13702fd28e890e27e342db8b669a4c84bb54a3783cb8926552',
|
||||
'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',
|
||||
|
||||
@@ -26,8 +26,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
versionCode 10423
|
||||
versionName "1.4.23"
|
||||
versionCode 10501
|
||||
versionName "1.5.1"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "TorVersion", "\"$tor_version\""
|
||||
|
||||
@@ -116,7 +116,6 @@ dependencies {
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
|
||||
|
||||
implementation 'org.briarproject:dont-kill-me-lib:0.2.5'
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "org.jsoup:jsoup:$jsoup_version"
|
||||
implementation 'info.guardianproject.panic:panic:1.0'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleAppComponent;
|
||||
@@ -25,12 +26,10 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.mailbox.ModularMailboxModule;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
@@ -84,6 +83,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.briarproject.briar.api.test.TestDataCreator;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.BuildConfig;
|
||||
import org.briarproject.briar.android.account.DozeHelperModule;
|
||||
import org.briarproject.briar.android.account.LockManagerImpl;
|
||||
import org.briarproject.briar.android.account.SetupModule;
|
||||
@@ -212,7 +211,7 @@ public class AppModule {
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
List<SimplexPluginFactory> simplex = new ArrayList<>();
|
||||
if (featureFlags.shouldEnableMailbox()) simplex.add(mailbox);
|
||||
simplex.add(mailbox);
|
||||
if (SDK_INT >= 19) simplex.add(drive);
|
||||
return simplex;
|
||||
}
|
||||
@@ -353,11 +352,6 @@ public class AppModule {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableMailbox() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateGroupsInCore() {
|
||||
return true;
|
||||
|
||||
@@ -15,12 +15,12 @@ import android.os.IBinder;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.account.AccountManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logout.HideUiActivity;
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.transition.Transition;
|
||||
import android.view.Window;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Wakeful;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.account.AccountManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.BriarService;
|
||||
import org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
||||
|
||||
@@ -83,15 +83,11 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
Preference prefMailbox =
|
||||
requireNonNull(findPreference(PREF_KEY_MAILBOX));
|
||||
if (viewModel.shouldEnableMailbox()) {
|
||||
prefMailbox.setOnPreferenceClickListener(preference -> {
|
||||
Intent i = new Intent(requireContext(), MailboxActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
prefMailbox.setVisible(false);
|
||||
}
|
||||
prefMailbox.setOnPreferenceClickListener(preference -> {
|
||||
Intent i = new Intent(requireContext(), MailboxActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference prefFeedback =
|
||||
requireNonNull(findPreference(PREF_KEY_FEEDBACK));
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException;
|
||||
import org.briarproject.briar.android.attachment.media.ImageCompressor;
|
||||
@@ -34,6 +33,7 @@ import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -161,10 +161,6 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
return featureFlags.shouldEnableProfilePictures();
|
||||
}
|
||||
|
||||
boolean shouldEnableMailbox() {
|
||||
return featureFlags.shouldEnableMailbox();
|
||||
}
|
||||
|
||||
private void loadOwnIdentityInfo() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.briarproject.briar.android.settings;
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.onionwrapper.CircumventionProvider;
|
||||
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference.SummaryProvider;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.splash;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
<string name="qr_code_too_old_1">Сканираният код за QR е от по-ранно издание на Briar.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
|
||||
<string name="qr_code_too_new_1">Сканираният код за QR е от по-ново издание на Briar.\n\nИнсталирайте последното издание и пробвайте отново.</string>
|
||||
<string name="mailbox_qr_code_for_contact">Сканираният код за QR е от Briar Пощенска кутия.\n\nАко искате да свържете Пощенска кутия използвайте Настройки > Пощенска кутия от менюто.</string>
|
||||
<string name="qr_code_format_unknown">Сканираният код за QR не предназначен за добавяне на контакт в Briar.\n\nЗа тази цел използвайте кода, на екрана на контакта ви.</string>
|
||||
<string name="qr_code_format_unknown">Сканираният код за QR не е предназначен за добавяне на контакт в Briar.\n\nЗа тази цел използвайте кода, на екрана на контакта ви.</string>
|
||||
<string name="camera_error">Грешка в камерата</string>
|
||||
<string name="connecting_to_device">Свързване с устройство\u2026</string>
|
||||
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
|
||||
@@ -602,6 +602,10 @@
|
||||
<string name="mailbox_setup_connecting">Свързване с Пощенска кутия…</string>
|
||||
<!--This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes".-->
|
||||
<string name="mailbox_setup_connecting_info">Може да отнеме %1s</string>
|
||||
<string name="mailbox_qr_code_too_old">Сканираният код за QR е от по-ранно издание на Briar Пощенска кутия.\n\nИнсталирайте последното издание и пробвайте отново.</string>
|
||||
<string name="mailbox_qr_code_too_new">Сканираният код за QR е от по-ново издание на Briar Пощенска кутия.\n\nИнсталирайте последното издание на Briar и пробвайте отново.</string>
|
||||
<string name="contact_qr_code_for_mailbox">Сканираният код за QR е предназначен за добавяне на контакт в Briar.\n\nАко желаете да добавите контакт отворете списъка с контакти и дикоснете иконата с +.</string>
|
||||
<string name="mailbox_setup_qr_code_wrong_description">Сканираният код за QR не е от Briar Пощенска кутия.\n\n Отворете приложението Briar Пощенска кутия на устройството, на което е инсталирано и сканирайте кода зя QR, който то предостави.</string>
|
||||
<string name="mailbox_setup_already_paired_title">Пощенската кутия е вече свързана</string>
|
||||
<string name="mailbox_setup_already_paired_description">Прекъснете връзката с пощенската кутия от другото устройство и опитайте отново.</string>
|
||||
<string name="mailbox_setup_io_error_title">Грешка при свързване</string>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<string name="dnkm_xiaomi_dialog_body_old">1. Otevřete seznam nedávných aplikací (také známý jako přepínač aplikací)\n\n2. Přejeďte prstem dolů po obrazu Briaru pro zobrazení ikony zámku\n\n3. Pokud není zámek uzamčen, uzamkněte ho klepnutím na něj</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_new">1. Otevřete seznam nedávných aplikací (také známý jako přepínač aplikací)\n\n2. Pokud má Briar vedle svého názvu malý obrázek zámku, nemusíte nic dělat\n\n3. Pokud tam žádný zámek není, stiskněte a podržte obraz Briaru, dokud se nezobrazí tlačítko visacího zámku, a pak na něj klepněte</string>
|
||||
<string name="dnkm_xiaomi_lock_apps_text">Klepněte na tlačítko níže pro otevření nastavení zabezpečení. Klepněte na „Zvýšení rychlosti“, poté na „Uzamčení aplikací“ a ujistěte se, že je Briar nastaven na „Uzamčeno“.</string>
|
||||
<string name="dnkm_xiaomi_lock_apps_help">Pokud není Briar na obrazovce „Uzamknutí aplikací“ nastaven na „Uzamčeno“, nebude moci běžet na pozadí.</string>
|
||||
<string name="dnkm_xiaomi_lock_apps_help">Pokud není Briar na obrazovce „Uzamčení aplikací“ nastaven na „Uzamčeno“, nebude moci běžet na pozadí.</string>
|
||||
<string name="dnkm_warning_dozed_1">Briar nemůže běžet na pozadí</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Heslo</string>
|
||||
@@ -243,7 +243,7 @@
|
||||
<string name="messaging_too_many_attachments_toast">Pouze prvních %d obrázků bude odesláno</string>
|
||||
<string name="menu_contact">Kontakt</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Přidat kontakt nablízku</string>
|
||||
<string name="add_contact_title">Přidat kontakt v okolí</string>
|
||||
<string name="add_contact_error_two_way">Oskenovali jste vy i druhá strana QR kód toho druhého?</string>
|
||||
<string name="face_to_face">Musíte se osobně setkat s osobou, kterou si chcete přidat jako kontakt.\n\nToto v budoucnu zabrání komukoli, aby se za vás vydával nebo četl Vaše zprávy.</string>
|
||||
<string name="continue_button">Pokračovat</string>
|
||||
@@ -265,9 +265,9 @@
|
||||
<string name="connection_error_feedback">Pokud tento problém přetrvává, prosím <a href="feedback">odešlete zpětnou vazbu</a> abyste nám pomohli vylepšit aplikaci.</string>
|
||||
<string name="info_both_must_scan">Vy i kontakt musíte oskenovat QR kód toho druhého</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Přidat kontakt vzdáleně</string>
|
||||
<string name="add_contact_nearby_title">Přidat kontakt blízko</string>
|
||||
<string name="add_contact_remotely_title">Přidat kontakt vzdáleně</string>
|
||||
<string name="add_contact_remotely_title_case">Přidat vzdálený kontakt</string>
|
||||
<string name="add_contact_nearby_title">Přidat kontakt v okolí</string>
|
||||
<string name="add_contact_remotely_title">Přidat vzdálený kontakt</string>
|
||||
<string name="contact_link_intro">Sem vložte odkaz od vašeho kontaktu</string>
|
||||
<string name="contact_link_hint">Odkaz kontaktu</string>
|
||||
<string name="paste_button">Vložit</string>
|
||||
@@ -331,30 +331,30 @@
|
||||
<string name="peer_trust_level_stranger">Neznámý</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Uvést vaše kontakty</string>
|
||||
<string name="introduction_onboarding_text">Představte vzájemně vaše kontakty, aby se spolu mohli spojit.</string>
|
||||
<string name="introduction_menu_item">Vytvořit představení</string>
|
||||
<string name="introduction_onboarding_text">Představte vzájemně vaše kontakty, aby se spolu mohly spojit.</string>
|
||||
<string name="introduction_menu_item">Představit</string>
|
||||
<string name="introduction_activity_title">Vybrat kontakt</string>
|
||||
<string name="introduction_not_possible">Již máte jedno představení v běhu s těmito kontakty. Prosím nejprve nechte toto dokončit. Pokud vy nebo vaše kontakty nejste často online, může to nějakou dobu trvat.</string>
|
||||
<string name="introduction_message_title">Pozvat kontakty</string>
|
||||
<string name="introduction_message_hint">Přidat zprávu (volitelné)</string>
|
||||
<string name="introduction_button">Vytvořit představení</string>
|
||||
<string name="introduction_button">Představit</string>
|
||||
<string name="introduction_sent">Vaše představení bylo odesláno.</string>
|
||||
<string name="introduction_error">Vyskytla se chyba při tvorbě představení.</string>
|
||||
<string name="introduction_request_sent">Požádali jste o pozvání %1$s do %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s vás požádal o uvedení do %2$s. Chcete přidat %2$s mezi vaše kontakty?</string>
|
||||
<string name="introduction_request_exists_received">%1$s vás požádal o představení s %2$s, ale %2$s je již ve vašich kontaktech. Pravděpodobně to %1$s neví, ale stále můžete odpovědět:</string>
|
||||
<string name="introduction_request_answered_received">%1$s vás žádá o pozvání do %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Přijali jste představení do %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent_info">Před tím, než bude přidán %1$s do kontaktů, musí souhlasit s představením. Toto může nějakou dobu trvat.</string>
|
||||
<string name="introduction_response_declined_sent">Odmítli jste představení do %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Přijali jste představení %1$s.</string>
|
||||
<string name="introduction_response_accepted_sent_info">Před tím, než bude %1$s přidán(a) do kontaktů, musí přijmout představení. Toto může nějakou dobu trvat.</string>
|
||||
<string name="introduction_response_declined_sent">Odmítli jste představení %1$s.</string>
|
||||
<string name="introduction_response_declined_auto">Představení %1$s bylo automaticky zamítnuto.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s přijal představení do %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s odmítl představení do %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s řekl, že %2$s odmítl představení.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s přijal(a) představení %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s odmítl(a) představení %2$s.</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$s řekl(a), že %2$s odmítl(a) představení.</string>
|
||||
<!--Connect via Bluetooth-->
|
||||
<string name="menu_item_connect_via_bluetooth">Spojení přes Bluetooth</string>
|
||||
<string name="connect_via_bluetooth_title">Spojení přes Bluetooth</string>
|
||||
<string name="connect_via_bluetooth_intro">V případě, že Bluetooth připojení nefunguje automaticky, můžete použít tuto obrazovku pro ruční připojení.\n\nVáš kontakt musí být poblíž, aby to fungovalo.\n\nVy i váš kontakt musíte klepnout na \"Start\" ve stejný čas.</string>
|
||||
<string name="menu_item_connect_via_bluetooth">Připojit přes Bluetooth</string>
|
||||
<string name="connect_via_bluetooth_title">Připojit přes Bluetooth</string>
|
||||
<string name="connect_via_bluetooth_intro">V případě, že Bluetooth připojení nefunguje automaticky, můžete použít tuto obrazovku pro ruční připojení.\n\nVáš kontakt musí být v okolí, aby to fungovalo.\n\nVy i váš kontakt musíte klepnout na \"Start\" ve stejný čas.</string>
|
||||
<string name="connect_via_bluetooth_already_discovering">Již se pokoušíte o připojení přes Bluetooth. Zkuste to znovu za chvíli.</string>
|
||||
<string name="connect_via_bluetooth_no_location_permission">Není možné pokračovat bez povolení znát umístění</string>
|
||||
<string name="connect_via_bluetooth_no_bluetooth_permission">Není možné pokračovat bez povolení přístupu k zařízením v okolí</string>
|
||||
@@ -415,7 +415,7 @@
|
||||
<string name="sharing_status_groups">Pouze zakladatel může pozvat nové členy do skupiny. Níže jsou všichni současní členové skupiny.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Odkrýt kontakty</string>
|
||||
<string name="groups_reveal_dialog_message">Můžete si vybrat, zda chcete své kontakty odhalit všem současným i budoucím členům této skupiny.\n\nPřidáním kontaktů je vaše připojení ke skupině rychlejší a spolehlivější, protože můžete komunikovat s odhalenými kontakty i když je tvůrce skupiny offline.</string>
|
||||
<string name="groups_reveal_dialog_message">Můžete si vybrat, zda chcete své kontakty odhalit všem současným i budoucím členům této skupiny.\n\nPřidáním kontaktů je vaše připojení ke skupině rychlejší a spolehlivější, protože můžete komunikovat s odhalenými kontakty i když je zakladatel skupiny offline.</string>
|
||||
<string name="groups_reveal_visible">Vztah s kontaktem je viditelný ve skupině</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">Vztah s kontaktem je viditelný ve skupině (Tebou odkrytý)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">Vztah s kontaktem je viditelný ve skupině (byl odkrytý %s)</string>
|
||||
@@ -495,7 +495,7 @@
|
||||
<string name="blogs_remove_blog_ok">Odstranit</string>
|
||||
<string name="blogs_blog_removed">Blog odstraněn</string>
|
||||
<string name="blogs_reblog_comment_hint">Přidat komentář (volitelné)</string>
|
||||
<string name="blogs_reblog_button">Reblog</string>
|
||||
<string name="blogs_reblog_button">Znovu-zveřejnit</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Sdílet blog</string>
|
||||
<string name="blogs_sharing_error">Vyskytla se chyba při sdílení tohoto blogu.</string>
|
||||
@@ -518,7 +518,7 @@
|
||||
<string name="blogs_rss_feeds_import_hint">Zadejte URL adresu RSS kanálu</string>
|
||||
<string name="blogs_rss_feeds_import_progress">Importování RSS kanálu...</string>
|
||||
<string name="blogs_rss_feeds_import_error">Omlouváme se! Vyskytla se chyba při importu vašeho kanálu.</string>
|
||||
<string name="blogs_rss_feeds_import_title">Importovat kanál ze souboru</string>
|
||||
<string name="blogs_rss_feeds_import_title">Import kanálu ze souboru</string>
|
||||
<string name="blogs_rss_feeds">RSS kanály</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Importováno:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Autor:</string>
|
||||
@@ -527,7 +527,7 @@
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Jste si jisti, že chcete odebrat tento kanál?\n\nPříspěvky budou odebrány z vašeho zařízení ale zůstanou na zařízeních jiných lidí.\n\nKontakty, se kterými jste tento kanál sdíleli, mohou přestat dostávat aktualizace.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Odstranit</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Žádné RSS kanály k zobrazení\n\nKlikněte na ikonu + pro nahrání příspěvků</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Vyskytl se problém s načtením vašeho kanálu příspěvků. Zkuste to prosím později.</string>
|
||||
<string name="blogs_rss_feeds_manage_error">Vyskytl se problém při načtení vašich kanálů. Zkuste to prosím později.</string>
|
||||
<!--Settings Profile Picture-->
|
||||
<string name="change_profile_picture">Klepněte pro změnu svého profilového obrázku</string>
|
||||
<string name="dialog_confirm_profile_picture_title">Změnit profilový obrázek</string>
|
||||
@@ -538,13 +538,13 @@
|
||||
<string name="pref_language_changed">Toto nastavení bude mít efekt když vykonáre restart svého Briar. Prosím odhlaste se a restartujte Briar.</string>
|
||||
<string name="pref_language_default">Podle systému</string>
|
||||
<string name="display_settings_title">Zobrazení</string>
|
||||
<string name="pref_theme_title">Vzhled</string>
|
||||
<string name="pref_theme_title">Motiv</string>
|
||||
<string name="pref_theme_light">Světlý</string>
|
||||
<string name="pref_theme_dark">Temný</string>
|
||||
<string name="pref_theme_auto">Automaticky (denní doba)</string>
|
||||
<string name="pref_theme_system">Podle systému</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">Connections</string>
|
||||
<string name="network_settings_title">Spojení</string>
|
||||
<string name="bluetooth_setting">Připojit se ke kontaktům přes Bluetooth</string>
|
||||
<string name="wifi_setting">Připojte se ke kontaktům na stejné Wi-Fi síti</string>
|
||||
<string name="tor_enable_title">Připojte se ke kontaktům přes internet</string>
|
||||
@@ -797,7 +797,7 @@
|
||||
<string name="transports_help_text">Briar se může připojit k vašim kontaktům pomocí internetu, Wi-Fi nebo Bluetooth.\n\nVšechna internetová spojení jsou kvůli soukromí přenášena sítí Tor.\n\nPokud kontakt může být dosažen pomocí několika metod, Briar je využije zároveň.</string>
|
||||
<!--Share app offline-->
|
||||
<string name="hotspot_title">Sdílet tuto aplikaci offline</string>
|
||||
<string name="hotspot_intro">Sdílejte tuto aplikaci s někým, kdo je blízko bez použití internetu, jen pomocí místní Wi-Fi sítě.
|
||||
<string name="hotspot_intro">Sdílejte tuto aplikaci s někým, kdo je v okolí a to bez použití internetu, jen pomocí místní Wi-Fi sítě.
|
||||
\n\nVáš telefon vytvoří Wi-Fi hotspot. Lidé v okolí se budou moci připojit k hotspotu a stáhnout aplikaci Briar z vašeho telefonu.</string>
|
||||
<string name="hotspot_button_start_sharing">Spustit hotspot</string>
|
||||
<string name="hotspot_button_stop_sharing">Zastavit hotspot</string>
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
<string name="exchanging_contact_details">تبادیل جزییات مخاطبu2026\</string>
|
||||
<string name="contact_added_toast">مخاطب اضافه شد: %s</string>
|
||||
<string name="contact_already_exists">مخاطب %s از قبل وجود دارد</string>
|
||||
<string name="contact_already_exists_general">مخاطب از قبل وجود دارد</string>
|
||||
<string name="qr_code_invalid">کد کیوآر نامعتبر می باشد</string>
|
||||
<string name="qr_code_too_old_1">کد QR که اسکن کردهاید مربوط به نسخه قدیمی Briar است.\n\nلطفا از مخاطب خود بخواهید به آخرین نسخه ارتقا دهد و سپس دوباره امتحان کنید.</string>
|
||||
<string name="qr_code_too_new_1">کد QR که اسکن کردهاید مربوط به نسخه جدیدتری از Briar است.\n\nلطفا به آخرین نسخه ارتقا دهید و سپس دوباره امتحان کنید.</string>
|
||||
@@ -453,6 +454,10 @@
|
||||
<string name="forum_declined_toast">دعوت نامه رد شد</string>
|
||||
<string name="shared_by_format">به اشتراک گذاشته شده توسط %s</string>
|
||||
<string name="forum_invitation_already_sharing">در حال به اشتراک گذاری</string>
|
||||
<string name="forum_invitation_already_invited">دعوتنامه قبلا ارسال شده است</string>
|
||||
<string name="forum_invitation_invite_received">دعوتنامه قبلا دریافت شده است</string>
|
||||
<string name="forum_invitation_not_supported">توسط این مخاطب پشتیبانی نمیشود</string>
|
||||
<string name="forum_invitation_error">خطا. این یک اشکال است و تقصیر شما نیست</string>
|
||||
<string name="forum_invitation_response_accepted_sent">شما دعوت نامه فروم از %s را پذیرفتید.</string>
|
||||
<string name="forum_invitation_response_declined_sent">شما دعوت نامه فروم از %s را رد کردید.</string>
|
||||
<string name="forum_invitation_response_declined_auto">دعوت به تالار از %s به صورت خودکار رد شد.</string>
|
||||
@@ -509,7 +514,9 @@
|
||||
<string name="blogs_rss_feeds_import">وارد کردن خوراک RSS</string>
|
||||
<string name="blogs_rss_feeds_import_button">وارد کردن</string>
|
||||
<string name="blogs_rss_feeds_import_hint">آدرس خوراک RSS را وارد کنید</string>
|
||||
<string name="blogs_rss_feeds_import_progress">در حال وارد کردن خوراک RSS…</string>
|
||||
<string name="blogs_rss_feeds_import_error">متاسفیم! وارد کردن خوراک شما با خطا مواجه شده است.</string>
|
||||
<string name="blogs_rss_feeds_import_title">وارد کردن خوراک از پرونده</string>
|
||||
<string name="blogs_rss_feeds">خوراک های RSS</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">وارد شده:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">نویسنده:</string>
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
<string name="exchanging_contact_details">Scambio dettagli contatto\u2026</string>
|
||||
<string name="contact_added_toast">Contatto aggiunto: %s</string>
|
||||
<string name="contact_already_exists">Il contatto %s esiste già</string>
|
||||
<string name="contact_already_exists_general">Il contatto esiste già</string>
|
||||
<string name="qr_code_invalid">Il codice QR non è valido</string>
|
||||
<string name="qr_code_too_old_1">Il codice QR che hai scansionato proviene da una versione più vecchia di Briar.\n\nChiedi al tuo contatto di aggiornare all\'ultima versione e poi riprova.</string>
|
||||
<string name="qr_code_too_new_1">Il codice QR che hai scansionato proviene da una versione più recente di Briar.\n\nAggiorna all\'ultima versione e poi riprova.</string>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<string name="setup_name_explanation">あなたのニックネームは、常に、あなたが投稿するコンテンツとともに表示されます。プロフィール作成後、編集はできません。</string>
|
||||
<string name="setup_next">次へ</string>
|
||||
<string name="setup_password_intro">パスワードを選択</string>
|
||||
<string name="setup_password_explanation">Briarのアカウント情報はクラウドではなく、暗号化された端末に保存されます。アプリのアンインストールやパスワードを紛失した場合、アカウントへのアクセスとデータを回復する手段はありません。\n\n推測するのが難しい、長いパスワードを設定してください。ランダムな4単語やランダムな10文字と数字と記号を組み合わせたものなどです。</string>
|
||||
<string name="setup_password_explanation">Briarのアカウント情報はクラウドではなく、暗号化された端末に保存されます。アプリのアンインストールやパスワードを紛失した場合、アカウントへのアクセスとデータを回復する手段はありません。\n\n推測するのが難しい、長いパスワードを設定してください。無作為な4単語や無作為な10文字と数字と記号を組み合わせたものなどです。</string>
|
||||
<string name="dnkm_doze_intro">メッセージを受信するために、Briarはバックグラウンドで接続を維持する必要があります。</string>
|
||||
<string name="dnkm_doze_explanation">メッセージを受信するために、Briarはバックグラウンドで接続を維持する必要があります。 Briarが接続を維持できるように、バッテリーの最適化を無効にしてください。</string>
|
||||
<string name="dnkm_doze_explanation">メッセージを受信するために、Briarはバックグラウンドで接続を維持する必要があります。 Briarが接続を維持できるように、電池の最適化を無効にしてください。</string>
|
||||
<string name="choose_nickname">ニックネームを入力</string>
|
||||
<string name="choose_password">パスワードを入力</string>
|
||||
<string name="confirm_password">確認のため再度パスワードを入力</string>
|
||||
@@ -19,13 +19,14 @@
|
||||
<string name="don_t_ask_again">次からは尋ねない</string>
|
||||
<string name="dnkm_huawei_protected_text">下のボタンをタップして、「保護されたアプリ」画面で Briarが保護されていることを確認してください。</string>
|
||||
<string name="dnkm_huawei_protected_button">Briarを保護する</string>
|
||||
<string name="dnkm_huawei_protected_help">Briarが保護されたアプリのリストに追加されていないと、Briarはバックグラウンドで実行することができません。</string>
|
||||
<string name="dnkm_huawei_protected_help">Briarが保護されたアプリの一覧に追加されていないと、Briarはバックグラウンドで実行することができません。</string>
|
||||
<string name="dnkm_huawei_app_launch_text">下のボタンをタップして「アプリの起動」画面を開き、Briarが「手動で管理する」に設定されていることを確認してください。</string>
|
||||
<string name="dnkm_huawei_app_launch_help">「アプリ起動」画面で Briarを「手動で管理する」に設定していないと、バックグラウンドで動作させることができません。</string>
|
||||
<string name="dnkm_xiaomi_text">バックグラウンドで実行するには、Briarを最近のアプリのリストにロックする必要があります。</string>
|
||||
<string name="dnkm_xiaomi_text">バックグラウンドで実行するには、Briarを最近のアプリの一覧にロックする必要があります。</string>
|
||||
<string name="dnkm_xiaomi_button">Briarを保護する</string>
|
||||
<string name="dnkm_xiaomi_help">Briarが最近のアプリのリストにロックされていないと、バックグラウンドで実行することができません。</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_old">1. 最近使ったアプリのリスト(アプリスイッチャーともいう)を開いて下さい。\n\n2. Briarの画像を下にスワイプすると、南京錠のアイコンが表示されます。\n\n3. ロックされていない場合は、タップしてロックします。</string>
|
||||
<string name="dnkm_xiaomi_help">Briarが最近使ったアプリの一覧にロックされていないと、バックグラウンドで実行することができません。</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_old">1. 最近使ったアプリの一覧(アプリスイッチャーともいう)を開いて下さい。\n\n2. Briarの画像を下にスワイプすると、南京錠のアイコンが表示されます。\n\n3. 南京錠がロックされていない場合は、タップしてロックします。</string>
|
||||
<string name="dnkm_xiaomi_dialog_body_new">1. 最近使ったアプリの一覧(アプリスイッチャーともいう)を開いて下さい。\n\n2. Briarの名前の隣に南京錠の小さな画像があれば、何もする必要がありません。\n\n3. そこに南京錠がなければ、南京錠ボタンが現れるまでBriarの画像を押し続け、そしてBriarをタップしてください。</string>
|
||||
<string name="dnkm_warning_dozed_1">Briarはバックグラウンドで実行できませんでした</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">パスワード</string>
|
||||
@@ -38,31 +39,31 @@
|
||||
<string name="dialog_message_lost_password">Briarアカウントはクラウド上ではなく、暗号化さた上であなたの端末に保存さています。したがって、Briarはパスワードをリセットできません。アカウントを削除して、はじめからやり直しますか?\n\n注意:あなたのID、連絡先、メッセージは永久に失われます。</string>
|
||||
<string name="startup_failed_activity_title">Briarの起動に失敗</string>
|
||||
<string name="startup_failed_clock_error">お使いの端末の時計が正しくないため、Briarは起動できませんでした。\n\n端末の時計を正しい時刻に設定してから、もう一度試してください。</string>
|
||||
<string name="startup_failed_db_error">Briarは、あなたのアカウント、連絡先、メッセージを含むデータベースを開くことができませんでした。\n\nアプリを最新版にアップグレードしてもう一度お試しいただくか、パスワード入力画面で「パスワードを忘れました」を選択して新しいアカウントを設定してください。</string>
|
||||
<string name="startup_failed_data_too_old_error">あなたのアカウントは古いバージョンのアプリで作成されたもので、このバージョンでは開くことができません。\n\n古いバージョンを再インストールするか、パスワード入力画面で\'パスワードを忘れました\'を選択して新しいアカウントを設定する必要があります。</string>
|
||||
<string name="startup_failed_db_error">Briarは、あなたのアカウント、連絡先、メッセージを含むデータベースを開くことができませんでした。\n\nアプリを最新版にアップグレードしてもう一度お試しいただくか、パスワード入力画面で「パスワードを忘れました」を選択して新規アカウントを設定してください。</string>
|
||||
<string name="startup_failed_data_too_old_error">あなたのアカウントは古いバージョンのアプリで作成されたもので、このバージョンでは開くことができません。\n\n古いバージョンを再インストールするか、パスワード入力画面で「パスワードを忘れました」を選択して新規アカウントを設定する必要があります。</string>
|
||||
<string name="startup_failed_data_too_new_error">あなたのアカウントは、このアプリの新しいバージョンで作成されたもので、このバージョンでは開くことができません。\n\n最新版にアップグレードしてから、もう一度試してください。</string>
|
||||
<string name="startup_failed_service_error">Briarは要求されたコンポーネントを起動できませんでした。\n\nアプリの最新版にアップグレードしてから、もう一度試してください。</string>
|
||||
<string name="startup_failed_service_error">Briarは必要なコンポーネントを起動できませんでした。\n\nアプリの最新版にアップグレードしてから、もう一度試してください。</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="other">これは、Briarのテストバージョンです。 アカウントはあと%d日で期限切れになり、更新できません。</item>
|
||||
<item quantity="other">これは、Briarの試験バージョンです。 アカウントはあと%d日で期限切れになり、更新できません。</item>
|
||||
</plurals>
|
||||
<plurals name="old_android_expiry_warning">
|
||||
<item quantity="other">Android 4はサポートされなくなりました。Briarは(%d日後に)%s上での動作を停止します。新しい端末に Briarをインストールして、新しいアカウントを作成してください。</item>
|
||||
<item quantity="other">Android 4はサポートされなくなりました。Briarは(%d日後に)%s上での動作を停止します。新しい端末に Briarをインストールして、新規アカウントを作成してください。</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">このソフトウェアの有効期限が切れました。テストに参加してくださりありがとうございます!</string>
|
||||
<string name="download_briar">Briarの使用を続けるには、最新のリリースをダウンロードしてください。</string>
|
||||
<string name="create_new_account">新しいアカウントを作成する必要があります。同じニックネームも使用できます。</string>
|
||||
<string name="expiry_date_reached">このソフトウェアの有効期限が切れました。試験に参加してくださりありがとうございます!</string>
|
||||
<string name="download_briar">Briarを使用し続けるには、最新のリリースをダウンロードしてください。</string>
|
||||
<string name="create_new_account">新規アカウントを作成する必要があります。同じニックネームも使用できます。</string>
|
||||
<string name="download_briar_button">最新リリースをダウンロード</string>
|
||||
<string name="old_android_expiry_date_reached">BriarはAndroid 4では動作しなくなりました。\n新しい端末にBriarをインストールしてください。</string>
|
||||
<string name="old_android_delete_account">下のボタンをタップして、この端末からあなたのアカウントを削除できます。</string>
|
||||
<string name="delete_account_button">アカウントを削除</string>
|
||||
<string name="startup_open_database">データベースの復号化中…</string>
|
||||
<string name="startup_open_database">データベースを復号中…</string>
|
||||
<string name="startup_migrate_database">データベースをアップグレード中…</string>
|
||||
<string name="startup_compact_database">データベースの圧縮中…</string>
|
||||
<string name="startup_compact_database">データベースを圧縮中…</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">ナビゲーションを開く</string>
|
||||
<string name="nav_drawer_close_description">ナビゲーションを閉じる</string>
|
||||
<string name="nav_drawer_open_description">ナビゲーションドロワーを開く</string>
|
||||
<string name="nav_drawer_close_description">ナビゲーションドロワーを閉じる</string>
|
||||
<string name="contact_list_button">連絡先</string>
|
||||
<string name="groups_button">プライベートグループ</string>
|
||||
<string name="groups_button">非公開グループ</string>
|
||||
<string name="forums_button">フォーラム</string>
|
||||
<string name="blogs_button">ブログ</string>
|
||||
<!--This is part of the main menu. The app will be locked when this is tapped.-->
|
||||
@@ -73,41 +74,41 @@
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">インターネット</string>
|
||||
<string name="tor_device_status_online_wifi">電話機はWi-Fiでインターネットにアクセスできます</string>
|
||||
<string name="tor_device_status_online_mobile">電話機はモバイル データでインターネットにアクセスできます</string>
|
||||
<string name="tor_device_status_offline">電話機がインターネットに接続できません</string>
|
||||
<string name="tor_plugin_status_enabling">Briarはインターネットに接続中です…</string>
|
||||
<string name="tor_device_status_online_mobile">電話機はモバイルデータでインターネットにアクセスできます</string>
|
||||
<string name="tor_device_status_offline">電話機はインターネットに接続していません</string>
|
||||
<string name="tor_plugin_status_enabling">Briarはインターネットに接続中です</string>
|
||||
<string name="tor_plugin_status_active">Briarはインターネットに接続されました</string>
|
||||
<string name="tor_plugin_status_inactive">Briarはインターネットに接続できません</string>
|
||||
<string name="tor_plugin_status_disabled">Briarはインターネットを使用しないように設定されています</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">Briarはモバイルデータを使用しないように設定されています</string>
|
||||
<string name="tor_plugin_status_disabled_battery">Briarはバッテリー駆動時にインターネットを使用しないように設定されています</string>
|
||||
<string name="tor_plugin_status_disabled_battery">Briarは電池で動作する時に、インターネットを使用しないように設定されています</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">Briarはこの国でインターネットを使わないように設定されています</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="transport_lan_long">同じ Wi-Fi ネットワーク</string>
|
||||
<string name="lan_device_status_on">電話機は Wi-Fi に接続されました</string>
|
||||
<string name="transport_lan_long">同じWi-Fiネットワーク</string>
|
||||
<string name="lan_device_status_on">電話機はWi-Fiに接続されました</string>
|
||||
<string name="lan_device_status_off">電話機はWi-Fiに接続されていません</string>
|
||||
<string name="lan_plugin_status_enabling">BriarはWi-Fiネットワークに接続中です…</string>
|
||||
<string name="lan_plugin_status_enabling">BriarはWi-Fiネットワークに接続中です</string>
|
||||
<string name="lan_plugin_status_active">BriarはWi-Fiネットワークに接続されました</string>
|
||||
<string name="lan_plugin_status_inactive">BriarはWi-Fiネットワークに接続できません</string>
|
||||
<string name="lan_plugin_status_disabled">BriarはWi-Fiネットワークを使用しないように設定されています</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">携帯電話の Bluetooth はオンになっています</string>
|
||||
<string name="bt_device_status_off">携帯電話の Bluetooth はオフにされました</string>
|
||||
<string name="bt_plugin_status_enabling">BriarはBluetoothに接続中です…</string>
|
||||
<string name="bt_plugin_status_active">BriarはBluetoothに接続しました</string>
|
||||
<string name="bt_device_status_on">電話機のBluetoothはオンにされました</string>
|
||||
<string name="bt_device_status_off">電話機のBluetoothはオフにされました</string>
|
||||
<string name="bt_plugin_status_enabling">BriarはBluetoothに接続中です</string>
|
||||
<string name="bt_plugin_status_active">BriarはBluetoothに接続されました</string>
|
||||
<string name="bt_plugin_status_inactive">BriarはBluetoothに接続できません</string>
|
||||
<string name="bt_plugin_status_disabled">BriarはBluetoothを使用しないように設定されています</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Briarからサインアウト</string>
|
||||
<string name="reminder_notification_text">タップして再ログインします。</string>
|
||||
<string name="reminder_notification_channel_title">Briarサインインリマインダー</string>
|
||||
<string name="reminder_notification_dismiss">断る</string>
|
||||
<string name="reminder_notification_dismiss">退かせる</string>
|
||||
<string name="ongoing_notification_title">Briarにサインイン</string>
|
||||
<string name="ongoing_notification_text">Briarを開く</string>
|
||||
<string name="ongoing_notification_text">触れてBriarを開く。</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="other">%d件の新規プライベートメッセージ</item>
|
||||
<item quantity="other">%d件の新規非公開メッセージ</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="other">%d件の新規グループメッセージ</item>
|
||||
@@ -126,8 +127,8 @@
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="got_it">了解</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="accept">承認</string>
|
||||
<string name="decline">却下</string>
|
||||
<string name="accept">受諾</string>
|
||||
<string name="decline">辞退</string>
|
||||
<string name="online">オンライン</string>
|
||||
<string name="offline">オフライン</string>
|
||||
<string name="send">送信</string>
|
||||
@@ -137,14 +138,14 @@
|
||||
<string name="start">開始</string>
|
||||
<string name="finish">終了</string>
|
||||
<string name="no_data">データなし</string>
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="text_too_long">入力された文章が長すぎます。</string>
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">入力された文章が長すぎます</string>
|
||||
<string name="show_onboarding">ヘルプダイアログを表示</string>
|
||||
<string name="fix">修復する</string>
|
||||
<string name="help">ヘルプ</string>
|
||||
<string name="sorry">申し訳ありません</string>
|
||||
<string name="error_start_activity">あなたのシステム上で利用できません</string>
|
||||
<string name="status_heading">状態</string>
|
||||
<string name="error_start_activity">あなたのシステム上では利用できません</string>
|
||||
<string name="status_heading">状態: </string>
|
||||
<string name="error">エラー</string>
|
||||
<string name="info">情報</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
@@ -153,14 +154,14 @@
|
||||
<string name="date_no_private_messages">メッセージがありません。</string>
|
||||
<string name="no_private_messages">表示するメッセージがありません</string>
|
||||
<string name="message_hint">新しいメッセージ</string>
|
||||
<string name="message_hint_auto_delete">新しい消えるメッセージ</string>
|
||||
<string name="message_hint_auto_delete">新規の消えるメッセージ</string>
|
||||
<string name="message_error">メッセージ送信エラー</string>
|
||||
<string name="image_caption_hint">説明文を追加する(任意)</string>
|
||||
<string name="image_attach">画像を添付</string>
|
||||
<string name="image_attach_error">画像を添付できませんでした</string>
|
||||
<string name="image_attach_error_too_big">画像が大きすぎます。 %dMBが制限です。</string>
|
||||
<string name="image_attach_error_too_big">画像が大きすぎます。上限は%dMBです。</string>
|
||||
<string name="image_attach_error_invalid_mime_type">サポートされていない画像形式:%s</string>
|
||||
<string name="set_contact_alias">連絡先を変更</string>
|
||||
<string name="set_contact_alias">連絡先名を変更</string>
|
||||
<string name="set_contact_alias_hint">連絡先名</string>
|
||||
<string name="menu_item_disappearing_messages">消えるメッセージ</string>
|
||||
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
|
||||
@@ -180,14 +181,14 @@
|
||||
</plurals>
|
||||
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
|
||||
<string name="auto_delete_msg_contact_disabled">%1$sのメッセージは消えません。%2$s</string>
|
||||
<string name="tap_to_learn_more">タップすると詳細が表示されます。</string>
|
||||
<string name="auto_delete_changed_warning_title">消えるメッセージが変更されました</string>
|
||||
<string name="tap_to_learn_more">タップして詳しく。</string>
|
||||
<string name="auto_delete_changed_warning_title">消えるメッセージを変更しました</string>
|
||||
<string name="auto_delete_changed_warning_message_enabled">メッセージの作成を開始してから、消えるメッセージが有効になりました。</string>
|
||||
<string name="auto_delete_changed_warning_message_disabled">メッセージの作成を開始してから、消えるメッセージは無効になりました。</string>
|
||||
<string name="auto_delete_changed_warning_send">とりあえず送る</string>
|
||||
<string name="auto_delete_changed_warning_send">それでも送る</string>
|
||||
<string name="delete_all_messages">全てのメッセージを削除</string>
|
||||
<string name="dialog_title_delete_all_messages">メッセージの削除時に確認</string>
|
||||
<string name="dialog_message_delete_all_messages">本当に全てのメッセージを削除してもよろしいですか?</string>
|
||||
<string name="dialog_message_delete_all_messages">全てのメッセージを削除してもよろしいですか?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">全てのメッセージを削除不可能</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_both">継続中の招待と紹介に関わるメッセージは、終了するまで削除できません。</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_introductions">継続中の紹介に関わるメッセージは、終了するまで削除できません。</string>
|
||||
@@ -195,9 +196,9 @@
|
||||
<string name="dialog_message_not_deleted_not_all_selected_both">招待と紹介を削除するには、要求と応答を選択する必要があります。</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_introductions">紹介を削除するには、要求と応答を選択する必要があります。</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_invitations">招待を削除するには、要求と応答を選択する必要があります。</string>
|
||||
<string name="delete_contact">この連絡先を削除</string>
|
||||
<string name="delete_contact">連絡先を削除</string>
|
||||
<string name="dialog_title_delete_contact">連絡先の削除時に確認</string>
|
||||
<string name="dialog_message_delete_contact">この連絡先と、この連絡先とのすべてのメッセージを削除してもよろしいですか?</string>
|
||||
<string name="dialog_message_delete_contact">この連絡先と、この連絡先と交わした全てのメッセージを削除してもよろしいですか?</string>
|
||||
<string name="contact_deleted_toast">連絡先を削除しました</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">あなた</string>
|
||||
@@ -206,9 +207,9 @@
|
||||
<string name="dialog_message_save_image">画像を保存すると、他のアプリがその画像にアクセスできるようになります。\n\n保存してもよろしいですか?</string>
|
||||
<string name="save_image_success">画像を保存しました</string>
|
||||
<string name="save_image_error">画像を保存できませんでした</string>
|
||||
<string name="dialog_title_no_image_support">利用できない画像です</string>
|
||||
<string name="dialog_message_no_image_support">あなたのこの連絡先のBriarは画像の添付をまだサポートしていません。連絡先がアップグレードすると、別のアイコンが表示されます。</string>
|
||||
<string name="dialog_title_image_support">この連絡先に画像を送信できるようになりました</string>
|
||||
<string name="dialog_title_no_image_support">画像は利用できません</string>
|
||||
<string name="dialog_message_no_image_support">あなたの連絡先のBriarは画像の添付をまだサポートしていません。連絡先がアップグレードすると、別のアイコンが表示されます。</string>
|
||||
<string name="dialog_title_image_support">今はこの連絡先に画像を送信できます</string>
|
||||
<string name="dialog_message_image_support">このアイコンをタップして画像を添付します。</string>
|
||||
<string name="messaging_too_many_attachments_toast">最初の%d個の画像のみが送信されます。</string>
|
||||
<string name="menu_contact">連絡先</string>
|
||||
@@ -216,64 +217,65 @@
|
||||
<string name="add_contact_title">近くの人を連絡先に追加する</string>
|
||||
<string name="add_contact_error_two_way">お互いのQRコードを読み取りましたか?</string>
|
||||
<string name="face_to_face">連絡先として追加したい人と会う必要があります。\n\nこれにより、だれかがあなたになりすましたり、メッセージを読んだりするのを防ぐことができます。</string>
|
||||
<string name="continue_button">続ける</string>
|
||||
<string name="try_again_button">もう一度やり直してください</string>
|
||||
<string name="waiting_for_contact_to_scan">連絡先がスキャンして接続するのを待っています\u2026</string>
|
||||
<string name="continue_button">続行</string>
|
||||
<string name="try_again_button">再試行</string>
|
||||
<string name="waiting_for_contact_to_scan">連絡先が読み取って接続するのを待っています\u2026</string>
|
||||
<string name="exchanging_contact_details">連絡先の詳細を交換しています\ u2026</string>
|
||||
<string name="contact_added_toast">追加された連絡先:%s</string>
|
||||
<string name="contact_added_toast">連絡先を追加しました:%s</string>
|
||||
<string name="contact_already_exists">連絡先%sは既に存在しています </string>
|
||||
<string name="contact_already_exists_general">連絡先は既に存在します </string>
|
||||
<string name="qr_code_invalid">QRコードが無効です</string>
|
||||
<string name="qr_code_too_old_1">読み取ったQRコードは、Briarの古いバージョンから生じました。\n\n最新版へアップグレードしてもらって、もう一度お試しください。</string>
|
||||
<string name="qr_code_too_new_1">読み取ったQRコードは、新しいバージョンのBriarから生じました。\n\n最新版にアップグレードしてから、もう一度お試しください。</string>
|
||||
<string name="mailbox_qr_code_for_contact">読み取ったQRコードは、Briarメールボックスから生じるものです。\n\nメールボックスに関連付けたいならば、設定を選択してください> Briarメニューからメールボックス</string>
|
||||
<string name="mailbox_qr_code_for_contact">読み取ったQRコードは、Briarメールボックスから生じました。\n\nメールボックスに関連付けたいならば、設定を選択してください> Briarメニューからメールボックス</string>
|
||||
<string name="qr_code_format_unknown">読み取ったQRコードは、Briarの連絡先を追加するために示されたものではありません。\n\nあなたの連絡先の画面上に表示されたQRコードを読み取ってください。</string>
|
||||
<string name="camera_error">カメラエラー</string>
|
||||
<string name="connecting_to_device">端末に接続中\u2026</string>
|
||||
<string name="authenticating_with_device">端末同士での認証中\u2026</string>
|
||||
<string name="connection_error_title">連絡先に接続できませんでした</string>
|
||||
<string name="connection_error_feedback">この問題が解決しない場合、アプリを改善するために<a href="feedback">フィードバック</a>を送信してください。</string>
|
||||
<string name="connection_error_feedback">この問題が解決しない場合、アプリを改善するために<a href="feedback">フィードバックを送信</a>してください。</string>
|
||||
<string name="info_both_must_scan">お互いのQRコードを読み取る必要があります</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">離れた場所にいる相手を連絡先に追加</string>
|
||||
<string name="add_contact_nearby_title">近くにいる相手を連絡先に追加</string>
|
||||
<string name="add_contact_remotely_title">離れた場所にいる相手を連絡先に追加</string>
|
||||
<string name="contact_link_intro">連絡相手のリンクをここに入力してください</string>
|
||||
<string name="contact_link_hint">連絡相手のリンク</string>
|
||||
<string name="add_contact_remotely_title_case">離れた場所にいる人を連絡先に追加</string>
|
||||
<string name="add_contact_nearby_title">近くにいる人を連絡先に追加</string>
|
||||
<string name="add_contact_remotely_title">離れた場所にいる人を連絡先に追加</string>
|
||||
<string name="contact_link_intro">連絡先のリンクをここに入力してください</string>
|
||||
<string name="contact_link_hint">連絡先のリンク</string>
|
||||
<string name="paste_button">貼り付け</string>
|
||||
<string name="add_contact_button">連絡相手を追加</string>
|
||||
<string name="add_contact_button">連絡先を追加</string>
|
||||
<string name="copy_button">コピー</string>
|
||||
<string name="share_button">共有</string>
|
||||
<string name="send_link_title">リンクの交換</string>
|
||||
<string name="send_link_title">リンクを交換</string>
|
||||
<string name="add_contact_choose_nickname">ニックネームを選んでください</string>
|
||||
<string name="add_contact_choose_a_nickname">ニックネームを入力してください</string>
|
||||
<string name="nickname_intro">この連絡相手にニックネームを付けます。 あなただけがそれを見ることができます。</string>
|
||||
<string name="your_link">リンクを共有する連絡相手を追加する</string>
|
||||
<string name="nickname_intro">この連絡先にニックネームを付けます。 あなただけがそれを見ることができます。</string>
|
||||
<string name="your_link">追加したい連絡先にこのリンクを渡す</string>
|
||||
<string name="link_clip_label">Briarリンク</string>
|
||||
<string name="link_copied_toast">リンクをコピーしました</string>
|
||||
<string name="adding_contact_error">連絡先への追加中にエラーが発生しました。</string>
|
||||
<string name="adding_contact_error">連絡先を追加中にエラーが発生しました。</string>
|
||||
<string name="pending_contact_requests_snackbar">保留中の連絡先への追加要求があります</string>
|
||||
<string name="pending_contact_requests">連絡先追加要求</string>
|
||||
<string name="no_pending_contacts">保留中の連絡先追加リクエストはありません</string>
|
||||
<string name="waiting_for_contact_to_come_online">連絡相手がオンラインになるのを待っています…</string>
|
||||
<string name="no_pending_contacts">保留中の連絡先追加要求はありません</string>
|
||||
<string name="waiting_for_contact_to_come_online">連絡先がオンラインになるのを待っています…</string>
|
||||
<string name="connecting">接続中…</string>
|
||||
<string name="adding_contact">連絡先を追加しています…</string>
|
||||
<string name="adding_contact_failed">連絡先の追加に失敗しました</string>
|
||||
<string name="dialog_title_remove_pending_contact">削除の確認</string>
|
||||
<string name="dialog_message_remove_pending_contact">この連絡先はまだ追加中です。 今削除すると、追加されません。</string>
|
||||
<string name="own_link_error">あなたのリンクではなく連絡先のリンクを入力してください</string>
|
||||
<string name="own_link_error">あなたのリンクではなく、連絡先のリンクを入力してください</string>
|
||||
<string name="nickname_missing">ニックネームを入力してください</string>
|
||||
<string name="invalid_link">無効なリンク</string>
|
||||
<string name="unsupported_link">このリンクは、Briarの新しいバージョンからのものです。 最新版にアップグレードしてから、もう一度試してください。</string>
|
||||
<string name="intent_own_link">あなたのBriar自身を指すリンクを開きました。 別の追加したい連絡先を使用してください!</string>
|
||||
<string name="unsupported_link">このリンクはBriarの新しいバージョンからのものです。 最新版にアップグレードしてから、もう一度試してください。</string>
|
||||
<string name="intent_own_link">あなた自身のリンクを開きました。追加したい連絡先のリンクを使用してください!</string>
|
||||
<string name="missing_link">リンクを入力してください</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="other">%d 新しい連絡先が追加されました。</item>
|
||||
<item quantity="other">%d件の新規連絡先が追加されました。</item>
|
||||
</plurals>
|
||||
<string name="offline_state">インターネットに接続されていません</string>
|
||||
<string name="offline_state">インターネット接続なし</string>
|
||||
<string name="duplicate_link_dialog_title">重複リンク</string>
|
||||
<string name="duplicate_link_dialog_text_1">既に保留中の連絡先があります。リンク:%s</string>
|
||||
<string name="duplicate_link_dialog_text_1_contact">既に連絡先があります。リンク:%s</string>
|
||||
@@ -286,8 +288,8 @@
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">別の人</string>
|
||||
<string name="duplicate_link_dialog_text_3">%1$sと%2$sから同じリンクを受信しました。\n\nどちらかがあなたの連絡先の内容を知ろうとしている可能性があります。\n\n他の人から同じリンクを受け取ったことを伝えないでください。</string>
|
||||
<string name="different_person_button">別人</string>
|
||||
<string name="duplicate_link_dialog_text_3">%1$sと%2$sがあなたに同じリンクを送信しました。\n\nどちらかがあなたの連絡先が誰であるかを探ろうとしている可能性があります。\n\n同じリンクを他の人から受信したとは言わないでください。</string>
|
||||
<string name="pending_contact_updated_toast">保留中の連絡先が更新されました</string>
|
||||
<string name="info_both_must_enter_links">お互いのリンクを追加する必要があります</string>
|
||||
<!--Peer trust levels-->
|
||||
@@ -298,53 +300,53 @@
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">連絡先を紹介</string>
|
||||
<string name="introduction_onboarding_text">連絡先をお互いに紹介することで、Briarで繋がることができます。</string>
|
||||
<string name="introduction_menu_item">はじめに</string>
|
||||
<string name="introduction_menu_item">紹介する</string>
|
||||
<string name="introduction_activity_title">連絡先を選択</string>
|
||||
<string name="introduction_not_possible">これらの連絡先については、すでに1つの紹介が進行中です。 これが最初に完了するようにしてください。 あなたやあなたの連絡相手がめったにオンラインにならない場合、これには時間がかかることがあります。</string>
|
||||
<string name="introduction_not_possible">これらの連絡先と、すでに1つの紹介が進行中です。最初にこれを終了するため、これを許可してください。 あなたやあなたの連絡相手が滅多にオンラインにならない場合、時間がかかることがあります。</string>
|
||||
<string name="introduction_message_title">連絡相手を紹介する</string>
|
||||
<string name="introduction_message_hint">メッセージを追加する(任意)</string>
|
||||
<string name="introduction_button">はじめに</string>
|
||||
<string name="introduction_sent">こちらの構成情報を送信しました。</string>
|
||||
<string name="introduction_error">構成情報の作成中にエラーが発生しました。</string>
|
||||
<string name="introduction_button">紹介する</string>
|
||||
<string name="introduction_sent">紹介は送信されました。</string>
|
||||
<string name="introduction_error">紹介中にエラーが発生しました。</string>
|
||||
<string name="introduction_request_sent">%1$sを%2$sに追加するように要求しました。</string>
|
||||
<string name="introduction_request_received">%1$sから%2$sへの紹介を求められました。 連絡先リストに%2$sを追加しますか?</string>
|
||||
<string name="introduction_request_exists_received">%1$sから%2$sの紹介を求められましたが、%2$sは既に連絡先リストにあります。%1$sはそれを知らない可能性があるため、引き続き応じれます:</string>
|
||||
<string name="introduction_request_received">%1$sから%2$sへの紹介を求められました。 連絡先一覧に%2$sを追加しますか?</string>
|
||||
<string name="introduction_request_exists_received">%1$sから%2$sの紹介を求められましたが、%2$sは既に連絡先一覧にあります。%1$sはそれを知らない可能性があるため、引き続き応じれます:</string>
|
||||
<string name="introduction_request_answered_received">%1$sから%2$sへの紹介を求められました。</string>
|
||||
<string name="introduction_response_accepted_sent">%1$sの紹介を受け入れました。</string>
|
||||
<string name="introduction_response_accepted_sent_info">%1$sを連絡先に追加する前に、紹介を受け入れる必要があります。 これには時間がかかる場合があります。</string>
|
||||
<string name="introduction_response_accepted_sent">%1$sへの紹介を受諾しました。</string>
|
||||
<string name="introduction_response_accepted_sent_info">%1$sを連絡先に追加する前に、紹介を受諾する必要があります。 これには時間がかかる場合があります。</string>
|
||||
<string name="introduction_response_declined_sent">%1$sへの紹介を辞退しました。</string>
|
||||
<string name="introduction_response_declined_auto">%1$sへの紹介は自動的に辞退されました。</string>
|
||||
<string name="introduction_response_accepted_received">%1$sは%2$sの紹介を受け入れました。</string>
|
||||
<string name="introduction_response_accepted_received">%1$sは%2$sへの紹介を受諾しました。</string>
|
||||
<string name="introduction_response_declined_received">%1$sは%2$sへの紹介を辞退しました。</string>
|
||||
<string name="introduction_response_declined_received_by_introducee">%1$sによると、%2$sが紹介を辞退しました。</string>
|
||||
<!--Connect via Bluetooth-->
|
||||
<string name="menu_item_connect_via_bluetooth">Bluetooth経由で接続する</string>
|
||||
<string name="connect_via_bluetooth_title">Bluetooth経由で接続する</string>
|
||||
<string name="connect_via_bluetooth_intro">Bluetooth接続が自動的に行われない場合は、この画面を使って手動で接続することができます。\n\nこの機能を利用するには、あなたの連絡先が近くにある必要があります。\n\nあなたと連絡先が同時に\"開始\"を押してください。</string>
|
||||
<string name="connect_via_bluetooth_intro">Bluetooth接続が自動的に行われない場合は、この画面を使って手動で接続することができます。\n\nこの機能を利用するには、あなたの連絡先が近くにある必要があります。\n\nあなたと連絡先が同時に「開始」を押してください。</string>
|
||||
<string name="connect_via_bluetooth_already_discovering">既にBluetooth経由での接続を試行中です。すぐに再試行してください。</string>
|
||||
<string name="connect_via_bluetooth_no_location_permission">位置情報の権限なくして続行不可能</string>
|
||||
<string name="connect_via_bluetooth_no_bluetooth_permission">付近の端末の権限なくして続行不可能</string>
|
||||
<string name="connect_via_bluetooth_start">Bluetooth経由で接続中…</string>
|
||||
<string name="connect_via_bluetooth_success">Bluetooth経由で接続に成功</string>
|
||||
<string name="connect_via_bluetooth_error">Bluetooth経由で接続不可能。</string>
|
||||
<string name="connect_via_bluetooth_success">Bluetooth経由での接続に成功</string>
|
||||
<string name="connect_via_bluetooth_error">Bluetooth経由での接続が不可能。</string>
|
||||
<string name="connect_via_bluetooth_error_not_supported">Bluetoothは端末によってサポートされていません。</string>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">表示するグループがありません</string>
|
||||
<string name="groups_list_empty_action">「+」アイコンをタップしてグループを作成するか、連絡先に登録している誰かにグループを共有してもらう</string>
|
||||
<string name="groups_created_by">%sによって作成されました。</string>
|
||||
<string name="groups_created_by">%sが作成</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="other">%d通のメッセージ</item>
|
||||
<item quantity="other">%d件のメッセージ</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">このグループは空です</string>
|
||||
<string name="groups_group_is_dissolved">このグループは解散しました</string>
|
||||
<string name="groups_remove">削除</string>
|
||||
<string name="groups_create_group_title">プライベートグループ作成</string>
|
||||
<string name="groups_create_group_title">非公開グループ作成</string>
|
||||
<string name="groups_create_group_button">グループ作成</string>
|
||||
<string name="groups_create_group_invitation_button">招待を送信</string>
|
||||
<string name="groups_create_group_hint">プライベートグループに名前をつける</string>
|
||||
<string name="groups_create_group_hint">非公開グループに名前をつける</string>
|
||||
<string name="groups_invitation_sent">グループ招待状が送信されました</string>
|
||||
<string name="groups_member_list">メンバー一覧</string>
|
||||
<string name="groups_invite_members">メンバーを招待</string>
|
||||
<string name="groups_member_list">人員一覧</string>
|
||||
<string name="groups_invite_members">一員を招待</string>
|
||||
<string name="groups_member_created_you">グループを作成しました</string>
|
||||
<string name="groups_member_created">%sがグループを作成しました</string>
|
||||
<string name="groups_member_joined_you">グループに参加しました</string>
|
||||
@@ -352,14 +354,14 @@
|
||||
<string name="groups_leave">グループを脱退</string>
|
||||
<string name="groups_leave_dialog_title">グループをやめる確認</string>
|
||||
<string name="groups_leave_dialog_message">このグループを脱退してもよろしいですか?</string>
|
||||
<string name="groups_dissolve">グループを削除</string>
|
||||
<string name="groups_dissolve_dialog_title">グループの削除の確認</string>
|
||||
<string name="groups_dissolve_dialog_message">このグループを削除してもよろしいですか?\n\nグループを削除すると、他のすべてのメンバーは会話を続けることができなくなり、最新のメッセージは受信できなくなります。</string>
|
||||
<string name="groups_dissolve_button">削除</string>
|
||||
<string name="groups_dissolved_dialog_title">グループを削除しました</string>
|
||||
<string name="groups_dissolved_dialog_message">このグループの作成者はこのグループを削除しました。\n\nそのため、書き込まれたすべてのメンバーは会話を続けることができなくなり、最新のメッセージも受信できなくなりました。</string>
|
||||
<string name="groups_dissolve">グループを解散</string>
|
||||
<string name="groups_dissolve_dialog_title">グループ解散の確認</string>
|
||||
<string name="groups_dissolve_dialog_message">このグループを解散してもよろしいですか?\n\n他の全人員は会話を続けることができなくなり、最新のメッセージは受信できなくなります。</string>
|
||||
<string name="groups_dissolve_button">解散</string>
|
||||
<string name="groups_dissolved_dialog_title">グループは解散されました</string>
|
||||
<string name="groups_dissolved_dialog_message">このグループの作成者はグループを解散しました。\n\nもうグループにはメッセージを書けなくなり、過去に書かれた全ての投稿は受信できなくなる可能があります。</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">グループへ招待</string>
|
||||
<string name="groups_invitations_title">グループ招待</string>
|
||||
<string name="groups_invitations_invitation_sent">%1$sをグループ\"%2$s\"に招待しています。</string>
|
||||
<string name="groups_invitations_invitation_received">%1$sがあなたをグループ\"%2$s\"に招待しています。</string>
|
||||
<string name="groups_invitations_joined">グループに参加しました</string>
|
||||
@@ -367,39 +369,39 @@
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="other">%d個のオープングループへの招待</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">%sからのグループ招待を受け入れました。</string>
|
||||
<string name="groups_invitations_response_accepted_sent">%sからのグループ招待を受諾しました。</string>
|
||||
<string name="groups_invitations_response_declined_sent">%sからのグループへの招待を辞退しました。</string>
|
||||
<string name="groups_invitations_response_declined_auto">%sからのグループへの招待は自動的に辞退されました。</string>
|
||||
<string name="groups_invitations_response_accepted_received">%sはグループへの招待を受け入れました。</string>
|
||||
<string name="groups_invitations_response_accepted_received">%sはグループへの招待を受諾しました。</string>
|
||||
<string name="groups_invitations_response_declined_received">%sはグループへの招待を辞退しました。</string>
|
||||
<string name="sharing_status_groups">グループに新しいメンバーを招待できるのは、作成者のみです。 以下は、グループの現在の全員のメンバーです。</string>
|
||||
<string name="sharing_status_groups">グループに新規人員を招待できるのは作成者のみです。 以下は、グループの現在の全人員です。</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">連絡先を公開する</string>
|
||||
<string name="groups_reveal_dialog_message">このグループの現在および将来のすべてのメンバーに連絡先を公開するかどうかを選択できます。\n\n連絡先を公開すると、グループの作成者がオフラインの場合でも公開された連絡先と通信できるため、グループへの接続がより高速で信頼性が高くなります。</string>
|
||||
<string name="groups_reveal_visible">連絡先関係はグループに表示されます</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">連絡先の関係はグループに表示されます(あなたによって公開されました)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">連絡先の関係はグループに表示されます(%sによって公開されました)</string>
|
||||
<string name="groups_reveal_contacts">連絡先を明かす</string>
|
||||
<string name="groups_reveal_dialog_message">このグループの現在および将来の全人員に連絡先を明かすかどうかを選択できます。\n\n連絡先を明かすと、グループの作成者がオフラインの場合でも明かされた連絡先と通信できるため、グループへの接続がより高速で信頼性が高くなります。</string>
|
||||
<string name="groups_reveal_visible">連絡先の関係はグループに表示されます</string>
|
||||
<string name="groups_reveal_visible_revealed_by_us">連絡先の関係はグループに表示されます(あなたによって明かされました)</string>
|
||||
<string name="groups_reveal_visible_revealed_by_contact">連絡先の関係はグループに表示されます(%sによって明かされました)</string>
|
||||
<string name="groups_reveal_invisible">連絡先の関係はグループには表示されません</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">表示するフォーラムがありません</string>
|
||||
<string name="no_forums_action">「+」アイコンをタップしてフォーラムを作成するか、連絡先に登録している誰かにフォーラムを共有してもらいます</string>
|
||||
<string name="create_forum_title">フォーラムを作成</string>
|
||||
<string name="create_forum_title">フォーラム作成</string>
|
||||
<string name="choose_forum_hint">フォーラムの名前を選択してください</string>
|
||||
<string name="create_forum_button">フォーラムを作成</string>
|
||||
<string name="forum_created_toast">フォーラムが作成されました</string>
|
||||
<string name="no_forum_posts">表示する投稿がありません</string>
|
||||
<string name="no_posts">投稿なし</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="other">%dつの投稿</item>
|
||||
<item quantity="other">%d件の投稿</item>
|
||||
</plurals>
|
||||
<string name="forum_new_message_hint">新しい投稿</string>
|
||||
<string name="forum_message_reply_hint">新しい返信</string>
|
||||
<string name="btn_reply">返信</string>
|
||||
<string name="forum_leave">フォーラムを脱退</string>
|
||||
<string name="dialog_title_leave_forum">フォーラムをやめる確認</string>
|
||||
<string name="dialog_message_leave_forum">このフォーラムを脱退してもよろしいですか?\n\nこのフォーラムで共有した連絡先は更新の受信を停止します。</string>
|
||||
<string name="dialog_title_leave_forum">フォーラム脱退の確認</string>
|
||||
<string name="dialog_message_leave_forum">このフォーラムを脱退してもよろしいですか?\n\nこのフォーラムで共有した連絡先は、更新の受信を停止します。</string>
|
||||
<string name="dialog_button_leave">脱退</string>
|
||||
<string name="forum_left_toast">フォーラムをやめました</string>
|
||||
<string name="forum_left_toast">フォーラムを脱退しました</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">フォーラムを共有</string>
|
||||
<string name="contacts_selected">選択した連絡先</string>
|
||||
@@ -411,8 +413,8 @@
|
||||
<string name="forum_share_error">このフォーラムの共有中にエラーが発生しました。</string>
|
||||
<string name="forum_invitation_received">%1$sがフォーラム\"%2$s\"をあなたと共有しました。</string>
|
||||
<string name="forum_invitation_sent">フォーラム\"%1$s\"を%2$sと共有しました。</string>
|
||||
<string name="forum_invitations_title">フォーラムへの招待</string>
|
||||
<string name="forum_invitation_exists">既にこのフォーラムへの招待を受け入れています。\n\n招待をさらに受け入れると、フォーラムへの接続がより速く、より信頼できるものになります。</string>
|
||||
<string name="forum_invitations_title">フォーラム招待</string>
|
||||
<string name="forum_invitation_exists">既にこのフォーラムへの招待を受諾しています。\n\n招待をさらに受け入れると、フォーラムへの接続がより速く、より信頼できるものになります。</string>
|
||||
<string name="forum_joined_toast">フォーラムに参加しました</string>
|
||||
<string name="forum_declined_toast">招待を辞退しました</string>
|
||||
<string name="shared_by_format">%sによって共有されました。</string>
|
||||
@@ -421,13 +423,13 @@
|
||||
<string name="forum_invitation_invite_received">招待は既に受信されました</string>
|
||||
<string name="forum_invitation_not_supported">この連絡先によってサポートされていません</string>
|
||||
<string name="forum_invitation_error">エラー。これはバグか、そうでなければ、あなたの誤りです</string>
|
||||
<string name="forum_invitation_response_accepted_sent">%sからのフォーラムへの招待を受け入れました。</string>
|
||||
<string name="forum_invitation_response_accepted_sent">%sからのフォーラムへの招待を受諾しました。</string>
|
||||
<string name="forum_invitation_response_declined_sent">%sからのフォーラムへの招待を辞退しました。</string>
|
||||
<string name="forum_invitation_response_declined_auto">%sからのフォーラムへの招待は自動的に辞退されました。</string>
|
||||
<string name="forum_invitation_response_accepted_received">%sはフォーラムへの招待を受け入れました。</string>
|
||||
<string name="forum_invitation_response_accepted_received">%sはフォーラムへの招待を受諾しました。</string>
|
||||
<string name="forum_invitation_response_declined_received">%sはフォーラムへの招待を辞退しました。</string>
|
||||
<string name="sharing_status">共有ステータス</string>
|
||||
<string name="sharing_status_forum">フォーラムのメンバーは誰でも連絡先と共有できます。 このフォーラムを次の連絡先と共有しています。 他のメンバーが表示されない場合もあります。</string>
|
||||
<string name="sharing_status">共有状況</string>
|
||||
<string name="sharing_status_forum">フォーラム員は誰でも連絡先と共有できます。 このフォーラムを次の連絡先と共有しています。 他の人員が表示されない場合もあります。</string>
|
||||
<string name="shared_with">%1$dと共有(%2$d オンライン)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="other">連絡先に登録している人が共有している%dフォーラム</item>
|
||||
@@ -440,30 +442,30 @@
|
||||
<string name="blogs_write_blog_post_body_hint">ブログの投稿内容を入力してください</string>
|
||||
<string name="blogs_publish_blog_post">公開</string>
|
||||
<string name="blogs_blog_post_created">ブログ投稿が作成されました</string>
|
||||
<string name="blogs_blog_post_received">新しいブログの投稿を受け取りました</string>
|
||||
<string name="blogs_blog_post_received">新規ブログ投稿を受信しました</string>
|
||||
<string name="blogs_blog_post_scroll_to">スクロール:</string>
|
||||
<string name="blogs_feed_empty_state">表示する投稿がありません</string>
|
||||
<string name="blogs_feed_empty_state_action">登録している連絡先やブログからの投稿がここに表示されます\n\nペンアイコンをタップして投稿を書き込みます</string>
|
||||
<string name="blogs_remove_blog">ブログを削除</string>
|
||||
<string name="blogs_remove_blog_dialog_message">このブログを削除してもよろしいですか?\n\n投稿は端末から削除されますが、他の人の端末からは削除されません。\n\nこのブログを共有した人に更新の受信を停止します。</string>
|
||||
<string name="blogs_remove_blog_ok">解除</string>
|
||||
<string name="blogs_remove_blog_ok">削除</string>
|
||||
<string name="blogs_blog_removed">ブログを削除しました</string>
|
||||
<string name="blogs_reblog_comment_hint">コメントを追加する(任意)</string>
|
||||
<string name="blogs_reblog_button">リブログ</string>
|
||||
<string name="blogs_reblog_button">ブログ再掲</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">ブログを共有</string>
|
||||
<string name="blogs_sharing_error">このブログの共有中にエラーが発生しました。</string>
|
||||
<string name="blogs_sharing_error">このブログを共有中にエラーが発生しました。</string>
|
||||
<string name="blogs_sharing_button">ブログを共有</string>
|
||||
<string name="blogs_sharing_snackbar">選択した連絡先と共有するブログ</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">%sからのブログへの招待を受け入れました。</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">%sからのブログへの招待を受諾しました。</string>
|
||||
<string name="blogs_sharing_response_declined_sent">%sからのブログへの招待を辞退しました。</string>
|
||||
<string name="blogs_sharing_response_declined_auto">%sからのブログへの招待は自動的に辞退されました。</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%sはブログへの招待を受け入れました。</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%sはブログへの招待を受諾しました。</string>
|
||||
<string name="blogs_sharing_response_declined_received">%sはブログへの招待を辞退しました。</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$sがブログ\"%2$s\"をあなたと共有しました。</string>
|
||||
<string name="blogs_sharing_invitation_sent">ブログ\"%1$s\"を%2$sと共有しました。</string>
|
||||
<string name="blogs_sharing_invitations_title">ブログへの招待</string>
|
||||
<string name="blogs_sharing_joined_toast">ブログの購読</string>
|
||||
<string name="blogs_sharing_invitations_title">ブログ招待</string>
|
||||
<string name="blogs_sharing_joined_toast">ブログを購読</string>
|
||||
<string name="blogs_sharing_declined_toast">招待を辞退しました</string>
|
||||
<string name="sharing_status_blog">ブログを購読している人は誰でも、ブログを連絡先と共有できます。 このブログを次の連絡先と共有しています。 表示できない他の購読者もいる可能性があります。</string>
|
||||
<!--RSS Feeds-->
|
||||
@@ -479,7 +481,7 @@
|
||||
<string name="blogs_rss_feeds_manage_updated">最終更新:</string>
|
||||
<string name="blogs_rss_remove_feed">フィードを削除</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">このフィードを削除してもよろしいですか?\n\n投稿は端末から削除されますが、他の人の端末からは削除されません。\n\nこのフィードを共有した人は更新の受信を停止されます。</string>
|
||||
<string name="blogs_rss_remove_feed_ok">解除</string>
|
||||
<string name="blogs_rss_remove_feed_ok">削除</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">表示するRSSフィードはありません\n\n「+」アイコンをタップしてフィードをインポートします</string>
|
||||
<string name="blogs_rss_feeds_manage_error">フィードの読み込み中に問題が発生しました。 後でもう一度やり直してください。</string>
|
||||
<!--Settings Profile Picture-->
|
||||
@@ -493,14 +495,14 @@
|
||||
<string name="pref_language_default">システムのデフォルト</string>
|
||||
<string name="display_settings_title">表示</string>
|
||||
<string name="pref_theme_title">テーマ</string>
|
||||
<string name="pref_theme_light">ライト</string>
|
||||
<string name="pref_theme_dark">ダーク</string>
|
||||
<string name="pref_theme_light">明</string>
|
||||
<string name="pref_theme_dark">暗</string>
|
||||
<string name="pref_theme_auto">自動(昼間)</string>
|
||||
<string name="pref_theme_system">システムのデフォルト</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">接続</string>
|
||||
<string name="bluetooth_setting">Bluetooth経由で連絡先に接続</string>
|
||||
<string name="wifi_setting">同じ Wi-Fi ネットワークで連絡先に接続</string>
|
||||
<string name="wifi_setting">同じWi-Fiネットワークで連絡先に接続</string>
|
||||
<string name="tor_enable_title">インターネット経由で連絡先に接続</string>
|
||||
<string name="tor_enable_summary">全接続をプライバシーのためにTorネットワークを通す</string>
|
||||
<string name="tor_network_setting">Torネットワークの接続方法</string>
|
||||
@@ -512,7 +514,7 @@
|
||||
<string name="tor_network_setting_summary">自動:%1$s(%2$s内)</string>
|
||||
<string name="tor_mobile_data_title">モバイルデータを使用する</string>
|
||||
<string name="tor_only_when_charging_title">充電時にのみインターネットに接続する</string>
|
||||
<string name="tor_only_when_charging_summary">端末がバッテリーを使用している場合、インターネット接続を無効にする</string>
|
||||
<string name="tor_only_when_charging_summary">端末が電池で動作する時に、インターネット接続を無効にする</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">セキュリティ</string>
|
||||
<string name="pref_lock_title">アプリロック</string>
|
||||
@@ -522,25 +524,25 @@
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Briarを使用しない場合、%s後に自動的にロックします</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_1">1 分</string>
|
||||
<string name="pref_lock_timeout_1">1分</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_5">5 分</string>
|
||||
<string name="pref_lock_timeout_5">5分</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_15">15分</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_30">30分</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_60">1時間</string>
|
||||
<string name="pref_lock_timeout_never">しない</string>
|
||||
<string name="pref_lock_timeout_never">行わない</string>
|
||||
<string name="pref_lock_timeout_never_summary">Briarを自動的にロックしない</string>
|
||||
<string name="change_password">パスワードの変更</string>
|
||||
<string name="change_password">パスワードを変更</string>
|
||||
<string name="current_password">現在のパスワード</string>
|
||||
<string name="choose_new_password">新しいパスワード</string>
|
||||
<string name="confirm_new_password">新しいパスワードの確認</string>
|
||||
<string name="choose_new_password">新規パスワード</string>
|
||||
<string name="confirm_new_password">新規パスワードを確認</string>
|
||||
<string name="password_changed">パスワードが変更されました。</string>
|
||||
<string name="panic_setting">パニックボタンのセットアップ</string>
|
||||
<string name="panic_setting">パニックボタンをセットアップ</string>
|
||||
<string name="panic_setting_title">パニックボタン</string>
|
||||
<string name="panic_setting_hint">パニックボタンアプリを使用したときのBriarの反応を構成する</string>
|
||||
<string name="panic_setting_hint">パニックボタンアプリを使用したときのBriarの反応を設定する</string>
|
||||
<string name="panic_app_setting_title">パニックボタンアプリ</string>
|
||||
<string name="unknown_app">未知のアプリ</string>
|
||||
<string name="panic_app_setting_summary">アプリが設定されていません</string>
|
||||
@@ -556,9 +558,9 @@
|
||||
<string name="notification_settings_title">通知</string>
|
||||
<string name="notify_sign_in_title">サインインするように通知する</string>
|
||||
<string name="notify_sign_in_summary">電話機の起動時またはアプリの更新時にリマインダーを表示する</string>
|
||||
<string name="notify_private_messages_setting_title">プライベート・メッセージ</string>
|
||||
<string name="notify_private_messages_setting_summary">プライベートメッセージのアラートを表示する</string>
|
||||
<string name="notify_private_messages_setting_summary_26">プライベートメッセージのアラートを設定する</string>
|
||||
<string name="notify_private_messages_setting_title">非公開メッセージ</string>
|
||||
<string name="notify_private_messages_setting_summary">非公開メッセージのアラートを表示する</string>
|
||||
<string name="notify_private_messages_setting_summary_26">非公開メッセージのアラートを設定する</string>
|
||||
<string name="notify_group_messages_setting_title">グループメッセージ</string>
|
||||
<string name="notify_group_messages_setting_summary">グループメッセージのアラートを表示する</string>
|
||||
<string name="notify_group_messages_setting_summary_26">グループメッセージのアラートを設定する</string>
|
||||
@@ -568,7 +570,7 @@
|
||||
<string name="notify_blog_posts_setting_title">ブログ投稿</string>
|
||||
<string name="notify_blog_posts_setting_summary">ブログ投稿のアラートを表示する</string>
|
||||
<string name="notify_blog_posts_setting_summary_26">ブログ投稿のアラートを設定する</string>
|
||||
<string name="notify_vibration_setting">バイブレーション</string>
|
||||
<string name="notify_vibration_setting">振動</string>
|
||||
<string name="notify_sound_setting">音</string>
|
||||
<string name="notify_sound_setting_default">デフォルト着信音</string>
|
||||
<string name="notify_sound_setting_disabled">なし</string>
|
||||
@@ -576,11 +578,11 @@
|
||||
<string name="cannot_load_ringtone">着信音を読み込めません</string>
|
||||
<!--Mailbox-->
|
||||
<string name="mailbox_settings_title">メールボックス</string>
|
||||
<string name="mailbox_setup_title">メールボックスのセットアップ</string>
|
||||
<string name="mailbox_setup_title">メールボックスをセットアップ</string>
|
||||
<string name="mailbox_setup_intro">メールボックスはあなたがオフラインの間、連絡先があなたにメッセージを送信することを有効にします。メールボックスはあなたがオンラインになるまで、メッセージを受信し保管します。\n
|
||||
\n予備端末上にBriarのメールボックスアプリをインストールできます。それを電源とWi-Fiに接続し、常時オンラインにしてください。</string>
|
||||
<string name="mailbox_setup_download">最初に、Google PlayまたはBriarをダウンロードしたどこかで\"BriarMailbox\"を検索して、他の端末上にメールボックスアプリをインストールします。\n
|
||||
\nそして、メールボックスアプリによって表示されるQRコードを読み取って、Briarとあなたのメールボックス結びつけます。</string>
|
||||
<string name="mailbox_setup_download">最初に、Google PlayまたはBriarをダウンロードしたどこかで「BriarMailbox」を検索して、他の端末上にメールボックスアプリをインストールします。\n
|
||||
\nそして、メールボックスアプリによって表示されるQRコードを読み取って、Briarとあなたのメールボックスをリンクします。</string>
|
||||
<string name="mailbox_setup_download_link">ダウンロードリンクを共有</string>
|
||||
<string name="mailbox_setup_button_scan">メールボックスのQRコードを読み取る</string>
|
||||
<string name="permission_camera_qr_denied_body">あなたはカメラにアクセスすることを拒否しましたが、QRコードを読み取るには、カメラを使用する必要があります。\n\nアクセス権を付与することを考慮願います。</string>
|
||||
@@ -595,7 +597,7 @@
|
||||
<string name="mailbox_setup_already_paired_description">あなたの端末上のメールボックスのリンクを解き、再試行してください。</string>
|
||||
<string name="mailbox_setup_io_error_title">接続できません</string>
|
||||
<string name="mailbox_setup_io_error_description">双方の端末がインターネットに接続されていることを確かにして、再試行してください。</string>
|
||||
<string name="mailbox_setup_assertion_error_title">メールボックスのエラー</string>
|
||||
<string name="mailbox_setup_assertion_error_title">メールボックスにエラー</string>
|
||||
<string name="mailbox_setup_assertion_error_description">もし問題が続くのであれば、Briarアプリ経由で(匿名データ付きの)フィードバックを送信してください。</string>
|
||||
<string name="mailbox_setup_camera_error_description">カメラにアクセスできません。端末を再起動してから、もう一度試してみてください。</string>
|
||||
<string name="mailbox_setup_paired_title">接続済み</string>
|
||||
@@ -617,11 +619,11 @@
|
||||
<!--Example for string substitution: Last connection: 3min ago-->
|
||||
<string name="mailbox_status_connected_info">最終接続: %s</string>
|
||||
<!--Indicates that there never was a connection to the mailbox. Last connection: Never-->
|
||||
<string name="mailbox_status_connected_never">しない</string>
|
||||
<string name="mailbox_status_connected_never">一度もない</string>
|
||||
<string name="mailbox_status_unlink_button">リンク解除</string>
|
||||
<string name="mailbox_status_unlink_dialog_title">メールボックスをリンク解除しますか?</string>
|
||||
<string name="mailbox_status_unlink_dialog_question">本当にあなたのメールボックスをリンク解除してもよろしいですか?</string>
|
||||
<string name="mailbox_status_unlink_dialog_warning">メールボックスをリンク解除すると、Briarがオフライン中にメッセージを受け取れません。</string>
|
||||
<string name="mailbox_status_unlink_dialog_warning">メールボックスをリンク解除すると、Briarがオフライン中にメッセージを受信できません。</string>
|
||||
<string name="mailbox_status_unlink_no_wipe_title">メールボックスはリンク解除されました</string>
|
||||
<string name="mailbox_status_unlink_no_wipe_message">次回、メールボックス端末にアクセスした際に、メールボックスアプリを起動し、「リンク解除」ボタンをタップして処理を完了してください。\n\nメールボックス端末にアクセスできなくなった場合でも、ご安心ください。データは暗号化されているので、処理を完了しなくても安全なままです。</string>
|
||||
<string name="mailbox_status_unlink_success">メールボックスはリンク解除されました</string>
|
||||
@@ -650,9 +652,9 @@
|
||||
<string name="mailbox_error_wizard_info2">端末にアクセスしたら、この画面に戻って来てください。</string>
|
||||
<string name="mailbox_error_wizard_info3">以下のボタンを使用してメールボックスをリンク解除してください。\n\n古いメールボックスをリンク解除した後、いつでも新しいメールボックスをセットアップできます。</string>
|
||||
<!--About-->
|
||||
<string name="about_title">Tor Project について</string>
|
||||
<string name="about_title">バージョン情報</string>
|
||||
<string name="briar_version">Briarバージョン: %s</string>
|
||||
<string name="tor_version">Tor バージョン: %s</string>
|
||||
<string name="tor_version">Torバージョン: %s</string>
|
||||
<string name="links">リンク</string>
|
||||
<string name="briar_website">\u2022 <a href="">ウェブサイト</a></string>
|
||||
<string name="briar_source_code">\u2022 <a href="">ソースコード</a></string>
|
||||
@@ -663,12 +665,12 @@
|
||||
<!--Conversation Settings-->
|
||||
<string name="disappearing_messages_title">消えるメッセージ</string>
|
||||
<string name="disappearing_messages_explanation_long">この設定を有効にすると、
|
||||
この会話内での新しいメッセージは、自動的に7\u00A0日後に消えます。
|
||||
この会話内での新規メッセージは、自動的に7\u00A0日後に消えます。
|
||||
\n\nメッセージの送信者の複製用のカウントダウンは、メッセージが到着した後に始まります。
|
||||
受取人用のカウントダウンは、メッセージを読んでから始まります。
|
||||
\n\n消えるメッセージには、爆弾アイコンで印が付けられます。
|
||||
\n\n受取人は、あなたが送ったメッセージの複製を取れることに留意してください。
|
||||
\n\nこの設定を変更すると、すぐにあなたの新着メッセージで反映され、
|
||||
\n\nこの設定を変更すると、すぐにあなたの新規メッセージで反映され、
|
||||
連絡先があなたの次のメッセージを受信した時点で、その連絡先のメッセージに適用されます。
|
||||
連絡先もあなたと二人に、この設定を変更できます。</string>
|
||||
<string name="learn_more">詳細情報</string>
|
||||
@@ -715,20 +717,20 @@
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">Briarからサインアウト中…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">スクリーンオーバーレイが検出されました</string>
|
||||
<string name="screen_filter_body">別のアプリがBriarの画面上に描画しています。 セキュリティを保護するために、Briarは、別のアプリがBriarの画面上に描画している場合、タッチに応答しません。\n\n次のアプリが上に描画されている可能性があります:\n\n%1$s</string>
|
||||
<string name="screen_filter_title">画面オーバーレイが検出されました</string>
|
||||
<string name="screen_filter_body">他のアプリがBriarの上に描画されています。セキュリティ保護のため、他のアプリが上に描画しているときは、Briarに触れても反応しません。\n\n以下のアプリが上に描画されている可能性があります:\n\n%1$s</string>
|
||||
<string name="screen_filter_body_api_30">他のアプリがBriarの上に描画されています。セキュリティ保護のため、他のアプリが上に描画しているときは、Briarに触れても反応しません。\n\n以下のアプリを確認して、原因のアプリを見つけてください。</string>
|
||||
<string name="screen_filter_allow">これらのアプリがBriarの画面上に描画できるようにする</string>
|
||||
<string name="screen_filter_review_apps">アプリを確認</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">カメラへのアクセス許可</string>
|
||||
<string name="permission_camera_title">カメラの権限</string>
|
||||
<string name="permission_camera_request_body">QRコードを読み取るには、Briarはカメラにアクセスする必要があります。</string>
|
||||
<string name="permission_location_title">位置情報へのアクセスの許可</string>
|
||||
<string name="permission_location_request_body">Bluetooth端末を検出するには、Briarがあなたの位置情報へのアクセスを必要とします。\n\nBriarはあなたの場所を保存したり、誰とも共有したりしません。</string>
|
||||
<string name="permission_location_title">位置情報の権限</string>
|
||||
<string name="permission_location_request_body">Bluetooth端末を検出するには、Briarがあなたの位置情報へアクセスする権限を必要とします。\n\nBriarはあなたの場所を保存したり、誰とも共有したりしません。</string>
|
||||
<string name="permission_camera_location_title">カメラと位置情報</string>
|
||||
<string name="permission_camera_location_request_body">QRコードを読み取るには、Briarはカメラにアクセスする必要があります。\n\nBluetooth端末を検出するには、Briarは現在地情報にアクセスする許可が必要です。\n\nBriarは現在地を保存したり、誰とも共有したりしません。</string>
|
||||
<string name="permission_camera_location_request_body">QRコードを読み取るには、Briarはカメラにアクセスする権限を必要とします。\n\nBluetooth端末を検出するには、Briarは現在地情報にアクセスする許可が必要です。\n\nBriarは現在地を保存したり、誰とも共有したりしません。</string>
|
||||
<string name="permission_camera_bluetooth_title">カメラと付近の端末</string>
|
||||
<string name="permission_camera_bluetooth_request_body">QRコードを読み取るには、Briarはカメラにアクセスする必要があります。\n\nBluetooth端末を検出するには、Briarは付近の端末を探し接続する許可が必要です。</string>
|
||||
<string name="permission_camera_bluetooth_request_body">QRコードを読み取るには、Briarはカメラにアクセスする権限を必要とします。\n\nBluetooth端末を検出するには、Briarは付近の端末を探し接続する許可が必要です。</string>
|
||||
<string name="permission_camera_denied_body">あなたはカメラにアクセスすることを拒否しましたが、連絡先を追加するには、カメラを使用する必要があります。\n\nアクセス権を付与することを考慮願います。</string>
|
||||
<string name="permission_location_denied_body">あなたは位置情報にアクセスすることを拒否しましたが、BriarはBluetooth端末を発見するのに、この権限が必要です。\n\nアクセス権を付与することを考慮願います。</string>
|
||||
<string name="permission_location_setting_title">位置情報設定</string>
|
||||
@@ -748,7 +750,7 @@
|
||||
<string name="lock_is_locked">Briarはロックされています</string>
|
||||
<string name="lock_tap_to_unlock">タップしてロック解除</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">Briarは、インターネット、Wi-Fi、Bluetoothを介して連絡先に接続することができます。\n\nすべてのインターネット接続は、プライバシー保護のためにTorネットワークを経由します。\n\n複数の方法で連絡が取れる場合、Briarはそれらを並行して使用します。</string>
|
||||
<string name="transports_help_text">Briarは、インターネット、Wi-Fi、Bluetoothを介して連絡先に接続することができます。\n\n全てのインターネット接続は、プライバシー保護のためにTorネットワークを経由します。\n\n複数の方法で連絡が取れる場合、Briarはそれらを並行して使用します。</string>
|
||||
<!--Share app offline-->
|
||||
<string name="hotspot_title">このアプリをオフラインで共有</string>
|
||||
<string name="hotspot_intro">あなたの電話機のWi-Fiを使用して、インターネット接続なしで、近くの誰かとこのアプリを共有します。
|
||||
@@ -817,10 +819,10 @@
|
||||
<!--Transfer Data via Removable Drives-->
|
||||
<string name="removable_drive_menu_title">リムーバブルドライブ経由で接続する</string>
|
||||
<string name="removable_drive_intro">インターネット、Wi-FiまたはBluetooth経由であなたの連絡先と接続できない場合、Briarは例えばUSBメモリーまたはSDカードのような、リムーバルドライブ上でメッセージを転送することもできます。</string>
|
||||
<string name="removable_drive_explanation">インターネット、Wi-FiまたはBluetooth経由であなたの連絡先と接続できない場合、Briarは例えばUSBメモリーまたはSDカードのような、リムーバルドライブ上でメッセージを転送することもできます。\n\n\"データ送信\"ボタンを使用したとき、連絡先への送信を待っているデータは、リムーバブルドライブに書き込まれます。これはプライベートメッセージ、添付、ブログ、フォーラムとプライベートグループを含みます。\n\n連絡先はリムーバルドライブを受け取ったときに、Briar内にメッセージをインポートするため、\"データ受信\"ボタンを使用できます。</string>
|
||||
<string name="removable_drive_explanation">インターネット、Wi-FiまたはBluetooth経由であなたの連絡先と接続できない場合、Briarは例えばUSBメモリーまたはSDカードのような、リムーバルドライブ上でメッセージを転送することもできます。\n\n\"データ送信\"ボタンを使用したとき、連絡先への送信を待っているデータは、リムーバブルドライブに書き込まれます。これは非公開メッセージ、添付、ブログ、フォーラムと非公開グループを含みます。\n\n連絡先はリムーバルドライブを受け取ったときに、Briar内にメッセージをインポートするため、「データ受信」ボタンを使用できます。</string>
|
||||
<string name="removable_drive_title_send">データ送信</string>
|
||||
<string name="removable_drive_title_receive">データ受信</string>
|
||||
<string name="removable_drive_send_intro">暗号化されたメッセージを含む新しいファイルを作成するには、下のボタンをタップしてください。ファイルの保存先を選択できます。\n\nリムーバブルドライブ上にファイルを保存したければ、ドライブを今挿れてください。</string>
|
||||
<string name="removable_drive_send_intro">暗号化されたメッセージを含む新規ファイルを作成するには、下のボタンをタップしてください。ファイルの保存先を選択できます。\n\nリムーバブルドライブ上にファイルを保存したければ、ドライブを今挿れてください。</string>
|
||||
<string name="removable_drive_send_no_data">現在、この連絡先に送信待ちのメッセージはありません。</string>
|
||||
<string name="removable_drive_send_not_supported">この連絡先はBriarの古いバージョン、またはこの機能をサポートしない古い端末を使用中です。</string>
|
||||
<string name="removable_drive_send_button">エクスポート用ファイルを選択してください</string>
|
||||
@@ -830,10 +832,10 @@
|
||||
<string name="removable_drive_success_send_title">エクスポート成功</string>
|
||||
<string name="removable_drive_success_send_text">データのエクスポートが成功しました。28日以内に連絡先へファイルを転送することができます。\n\nファイルがリムーバブルドライブ上にある場合、それを着脱する前に、ステータスバー内の通知を利用してイジェクトしてください。</string>
|
||||
<string name="removable_drive_success_receive_title">インポート成功</string>
|
||||
<string name="removable_drive_success_receive_text">全ての暗号化されたメッセージは、受け取っているこのファイル内に含まれました。</string>
|
||||
<string name="removable_drive_success_receive_text">全ての暗号化されたメッセージは、受信しているこのファイル内に含まれました。</string>
|
||||
<string name="removable_drive_error_send_title">データエクスポートでエラー</string>
|
||||
<string name="removable_drive_error_send_text">ファイルへデータを書き込み中にエラーが発生しました。\n\nリムーバブルドライブを使用している場合、適切に挿入されていることを確かにして、再度お試しください。\n\nエラーが持続する場合、経験している問題について、Briarチームにフィードバックを送信してください。</string>
|
||||
<string name="removable_drive_error_receive_title">データインポートでエラー</string>
|
||||
<string name="removable_drive_error_receive_title">データインポート中にエラー</string>
|
||||
<string name="removable_drive_error_receive_text">選択されたファイルは、Briarが正しく認識するものが含まれていません。\n\n正しいファイルが選択されていることを確認してください。\n\n連絡先が28日より前にファイルを作成した場合、Briarは正しく認識することができません。</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
|
||||
@@ -256,6 +256,8 @@
|
||||
<string name="qr_code_invalid">Kod QR jest nieprawidłowy</string>
|
||||
<string name="qr_code_too_old_1">Zeskanowany kod QR pochodzi ze starszej wersji Briar.\n\nProszę poprosić Twój kontakt o aktualizację do najnowszej wersji, a następnie spróbować ponownie.</string>
|
||||
<string name="qr_code_too_new_1">Zeskanowany kod QR pochodzi z nowszej wersji programu Briar.\n\nProszę zaktualizować go do najnowszej wersji, a następnie spróbować ponownie.</string>
|
||||
<string name="mailbox_qr_code_for_contact">Zeskanowany kod QR pochodzi ze skrzynki odbiorczej Briar.\n\nJeśli chcesz połączyć skrzynkę odbiorczą, wybierz Ustawienia i Skrzynka odbiorcza z menu Briar.</string>
|
||||
<string name="qr_code_format_unknown">Zeskanowany kod QR nie jest przeznaczony do dodawania kontaktu z Briar.\n\nZeskanuj kod QR wyświetlany na ekranie kontaktu.</string>
|
||||
<string name="camera_error">Błąd aparatu</string>
|
||||
<string name="connecting_to_device">Łączenie z urządzeniem\u2026</string>
|
||||
<string name="authenticating_with_device">Autoryzowanie z urządzeniem\u2026</string>
|
||||
@@ -633,6 +635,7 @@
|
||||
<string name="mailbox_setup_connecting_info">Może to potrwać do %1s</string>
|
||||
<string name="mailbox_qr_code_too_old">Zeskanowany kod QR pochodzi ze starszej wersji skrzynki odbiorczej Briar.\n\nProszę zaktualizować ją do najnowszej wersji, a następnie spróbować ponownie.</string>
|
||||
<string name="mailbox_qr_code_too_new">Zeskanowany kod QR pochodzi z nowszej wersji skrzynki odbiorczej Briar.\n\nProszę zaktualizować ją do najnowszej wersji, a następnie spróbować ponownie.</string>
|
||||
<string name="contact_qr_code_for_mailbox">Zeskanowany kod QR służy do dodawania kontaktu Briar.\n\nJeśli chcesz dodać kontakt, przejdź do listy kontaktów i naciśnij ikonę +.</string>
|
||||
<string name="mailbox_setup_qr_code_wrong_description">Zeskanowany kod QR nie pochodzi ze skrzynki odbiorczej Briar. Otwórz skrzynkę odbiorczą Briar na urządzeniu z Twoją skrzynką i zeskanuj wyświetlany kod QR. </string>
|
||||
<string name="mailbox_setup_already_paired_title">Mailbox już podłączony</string>
|
||||
<string name="mailbox_setup_already_paired_description">Odłącz Mailbox na drugim urządzeniu i spróbuj ponownie.</string>
|
||||
@@ -772,10 +775,12 @@ Brak dostępu do aparatu. Spróbuj ponownie, może po ponownym uruchomieniu urz
|
||||
<string name="permission_camera_location_title">Kamera i lokalizacja</string>
|
||||
<string name="permission_camera_location_request_body">Aby zeskanować kod QR, Briar potrzebuje dostępu do kamery.\n\nAby odkryć urządzenia Bluetooth, Briar potrzebuje zezwolenia na dostęp do Twojej lokalizacji.\n\nBriar nie przechowuje Twojej lokalizacji ani nie udostępnia jej nikomu.</string>
|
||||
<string name="permission_camera_bluetooth_title">Aparat i urządzenia w pobliżu</string>
|
||||
<string name="permission_camera_bluetooth_request_body">Aby zeskanować kod QR, Briar potrzebuje dostępu do aparatu.\n\nAby wykryć urządzenia Bluetooth, Briar potrzebuje uprawnień do znajdowania i łączenia się z urządzeniami w pobliżu.</string>
|
||||
<string name="permission_camera_denied_body">Odmówiłeś dostępu do aparatu, lecz dodawanie kontaktów wymaga jego użycia.\n\nProszę rozważyć udzielenie dostępu do aparatu</string>
|
||||
<string name="permission_location_denied_body">Zabroniłeś dostępu do Twojej lokalizacji, ale Briar potrzebuje tego uprawnienia do wykrywania urządzeń Bluetooth.\n\nRozważ przyznanie tego dostępu.</string>
|
||||
<string name="permission_location_setting_title">Ustawienia lokalizacji</string>
|
||||
<string name="permission_location_setting_body">Ustawienie lokalizacji urządzenia musi być włączone, aby można było znaleźć inne urządzenia przez Bluetooth. Włącz lokalizację, aby kontynuować. Później możesz ją ponownie wyłączyć.</string>
|
||||
<string name="permission_location_setting_hotspot_body">Aby utworzyć hotspot Wi-Fi, musisz włączyć ustawienie lokalizacji urządzenia. Włącz lokalizację, aby kontynuować. Możesz ją później wyłączyć ponownie.</string>
|
||||
<string name="permission_location_setting_button">Włącz lokalizację</string>
|
||||
<string name="permission_bluetooth_title">Pozwolenie do urządzeń w pobliżu</string>
|
||||
<string name="permission_bluetooth_body">Aby korzystać z komunikacji Bluetooth, Briar potrzebuje uprawnień do znajdowania i łączenia się z pobliskimi urządzeniami.</string>
|
||||
|
||||
@@ -120,7 +120,6 @@ dependencyVerification {
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.65:bcprov-jdk15on-1.65.jar:e78f96eb59066c94c94fb2d6b5eb80f52feac6f5f9776898634f8addec6e2137',
|
||||
'org.briarproject:dont-kill-me-lib:0.2.5:dont-kill-me-lib-0.2.5.aar:55cd9d511b7016ab573905d64bc54e222e2633144d36389192b8b34485b31b9d',
|
||||
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
|
||||
@@ -136,12 +135,11 @@ dependencyVerification {
|
||||
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c',
|
||||
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.20:kotlin-stdlib-jdk7-1.6.20.jar:aa2fa2e81355c4d98dd97da2169bf401f842261378f5b1cbea1aa11855d67620',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
|
||||
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1:kotlinx-coroutines-android-1.4.1.jar:d4cadb673b2101f1ee5fbc147956ac78b1cfd9cc255fb53d3aeb88dff11d99ca',
|
||||
'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.1:kotlinx-coroutines-core-jvm-1.4.1.jar:6d2f87764b6638f27aff12ed380db4b63c9d46ba55dc32683a650598fa5a3e22',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
|
||||
|
||||
@@ -45,6 +45,7 @@ dependencies {
|
||||
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"
|
||||
|
||||
kapt "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ internal class HeadlessModule(private val appDir: File) {
|
||||
override fun shouldEnableImageAttachments() = false
|
||||
override fun shouldEnableProfilePictures() = false
|
||||
override fun shouldEnableDisappearingMessages() = false
|
||||
override fun shouldEnableMailbox() = false
|
||||
override fun shouldEnablePrivateGroupsInCore() = false
|
||||
override fun shouldEnableForumsInCore() = true
|
||||
override fun shouldEnableBlogsInCore() = true
|
||||
|
||||
@@ -36,12 +36,18 @@ dependencyVerification {
|
||||
'javax.servlet:javax.servlet-api:3.1.0:javax.servlet-api-3.1.0.jar:af456b2dd41c4e82cf54f3e743bc678973d9fe35bd4d3071fa05c7e5333b8482',
|
||||
'net.bytebuddy:byte-buddy-agent:1.12.6:byte-buddy-agent-1.12.6.jar:9b29421fe4650b75fc3ed53590f914c54f932e334b3506cc00296dff73024183',
|
||||
'net.bytebuddy:byte-buddy:1.12.6:byte-buddy-1.12.6.jar:211918dc24f0fdef4335ce8af40ef5616e15e818b962a21146397c7701eb75a7',
|
||||
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6',
|
||||
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff',
|
||||
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apiguardian:apiguardian-api:1.1.0:apiguardian-api-1.1.0.jar:a9aae9ff8ae3e17a2a18f79175e82b16267c246fbbd3ca9dfbbb290b08dcfdd4',
|
||||
'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:obfs4proxy-linux:0.0.14-tor2:obfs4proxy-linux-0.0.14-tor2.jar:bb2431092b5ad998ad620b0223e725c0f7e43f1b02af2f097a2544edc1fd9738',
|
||||
'org.briarproject:obfs4proxy-windows:0.0.14-tor2:obfs4proxy-windows-0.0.14-tor2.jar:b5fbd00a8c35ccf095b265370752390e4cd46055331049c4dfcc236dc9c650ac',
|
||||
'org.briarproject:onionwrapper-core:0.0.1:onionwrapper-core-0.0.1.jar:a1937506b00ee6620e909a500e5d004be81f94a6f7d7c898e1a9e841a8ae8a2a',
|
||||
'org.briarproject:onionwrapper-java:0.0.1:onionwrapper-java-0.0.1.jar:102ccea934d02b13702fd28e890e27e342db8b669a4c84bb54a3783cb8926552',
|
||||
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
|
||||
'org.briarproject:snowflake-windows:2.5.1:snowflake-windows-2.5.1.jar:700ec9c68dc033f544daa4ca3547c89e523aed66500cf4b3ac51fe017c51e7be',
|
||||
'org.briarproject:tor-linux:0.4.7.13-2:tor-linux-0.4.7.13-2.jar:1e4ca9e0f724e1f17fcce570832704942cc3be26c4c2eccbe5aae29f35afa307',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
google()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
@@ -19,7 +18,6 @@ allprojects {
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
maven {
|
||||
url 'https://plugins.gradle.org/m2/'
|
||||
@@ -40,6 +38,7 @@ buildscript {
|
||||
junit_version = "4.13.2"
|
||||
jmock_version = '2.12.0'
|
||||
mockwebserver_version = '4.9.3'
|
||||
onionwrapper_version = '0.0.1'
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||
|
||||
Reference in New Issue
Block a user