Compare commits

..

3 Commits

Author SHA1 Message Date
Sebastian Kürten
e6a4dd6ff5 WIP: for macos, use tor binaries from Tor Browser 2023-03-25 12:22:06 +01:00
Sebastian Kürten
d963e75bd1 WIP: remove tor binaries; add macos tor dependencies from tmp Maven repo 2023-03-18 09:02:48 +01:00
Sebastian Kürten
b5283146b1 WIP: macos support 2023-03-14 16:30:24 +01:00
70 changed files with 2677 additions and 479 deletions

View File

@@ -88,9 +88,25 @@ test_reproducible:
only:
- tags
mailbox integration test:
.optional_tests:
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 Normal file
View File

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

View File

@@ -13,8 +13,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
versionCode 10501
versionName "1.5.1"
versionCode 10423
versionName "1.4.23"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -40,8 +40,6 @@ 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
@@ -51,7 +49,6 @@ 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"

View File

@@ -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 and Nubia devices running API 32 -->
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="32" />
android:maxSdkVersion="31" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

View File

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

View File

@@ -1,16 +1,15 @@
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;
@@ -20,7 +19,6 @@ import dagger.Module;
AndroidSystemModule.class,
AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class,
AndroidWakeLockModule.class,
DefaultThreadFactoryModule.class,
CircumventionModule.class,
DnsModule.class,

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AndroidWakeLock {
/**
* Acquires the wake lock. This has no effect if the wake lock has already
* been acquired.
*/
void acquire();
/**
* Releases the wake lock. This has no effect if the wake lock has already
* been released.
*/
void release();
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
@NotNullByDefault
public interface AndroidWakeLockManager {
/**
* Creates a wake lock with the given tag. The tag is only used for
* logging; the underlying OS wake lock will use its own tag.
*/
AndroidWakeLock createWakeLock(String tag);
/**
* Runs the given task while holding a wake lock.
*/
void runWakefully(Runnable r, String tag);
/**
* Submits the given task to the given executor while holding a wake lock.
* The lock is released when the task completes, or if an exception is
* thrown while submitting or running the task.
*/
void executeWakefully(Runnable r, Executor executor, String tag);
/**
* Starts a dedicated thread to run the given task asynchronously. A wake
* lock is acquired before starting the thread and released when the task
* completes, or if an exception is thrown while starting the thread or
* running the task.
* <p>
* This method should only be used for lifecycle management tasks that
* can't be run on an executor.
*/
void executeWakefully(Runnable r, String tag);
}

View File

@@ -2,10 +2,10 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.app.Application;
import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -14,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;

View File

@@ -2,11 +2,11 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException;

View File

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

View File

@@ -2,11 +2,9 @@ package org.briarproject.bramble.plugin.tor;
import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -15,13 +13,12 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.AndroidTorWrapper;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.TorWrapper;
import java.io.File;
import java.util.concurrent.Executor;
@@ -31,7 +28,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
@Immutable
@@ -43,13 +39,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -59,8 +55,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@TorControlPort int torControlPort,
Application app,
AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app;
@@ -83,18 +79,12 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
ioExecutor, eventExecutor, architecture, torDirectory,
torSocksPort, torControlPort);
// Android versions 7.1 and newer can verify Let's Encrypt TLS certs
// signed with the IdentTrust DST Root X3 certificate. Older versions
// of Android consider the certificate to have expired at the end of
// September 2021.
boolean canVerifyLetsEncryptCerts = SDK_INT >= 25;
return new TorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, tor, callback, MAX_LATENCY,
MAX_IDLE_TIME, canVerifyLetsEncryptCerts);
return new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -68,4 +69,11 @@ public class AndroidSystemModule {
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
return provider;
}
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(
AndroidWakeLockManagerImpl wakeLockManager) {
return wakeLockManager;
}
}

View File

@@ -8,10 +8,10 @@ import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.nullsafety.NotNullByDefault;

View File

@@ -2,9 +2,9 @@ package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.ScheduledExecutorService;

View File

@@ -0,0 +1,74 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.FINE;
import static java.util.logging.Logger.getLogger;
/**
* A wrapper around a {@link SharedWakeLock} that provides the more convenient
* semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release()
* don't need to be balanced).
*/
@ThreadSafe
@NotNullByDefault
class AndroidWakeLockImpl implements AndroidWakeLock {
private static final Logger LOG =
getLogger(AndroidWakeLockImpl.class.getName());
private static final AtomicInteger INSTANCE_ID = new AtomicInteger(0);
private final SharedWakeLock sharedWakeLock;
private final String tag;
private final Object lock = new Object();
@GuardedBy("lock")
private boolean held = false;
AndroidWakeLockImpl(SharedWakeLock sharedWakeLock, String tag) {
this.sharedWakeLock = sharedWakeLock;
this.tag = tag + "_" + INSTANCE_ID.getAndIncrement();
}
@Override
public void acquire() {
synchronized (lock) {
if (held) {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " already acquired");
}
} else {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " acquiring shared wake lock");
}
held = true;
sharedWakeLock.acquire();
}
}
}
@Override
public void release() {
synchronized (lock) {
if (held) {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " releasing shared wake lock");
}
held = false;
sharedWakeLock.release();
} else {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " already released");
}
}
}
}
}

View File

@@ -0,0 +1,125 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
/**
* How often to replace the wake lock.
*/
private static final long LOCK_DURATION_MS = MINUTES.toMillis(1);
/**
* Automatically release the lock this many milliseconds after it's due
* to have been replaced and released.
*/
private static final long SAFETY_MARGIN_MS = SECONDS.toMillis(30);
private final SharedWakeLock sharedWakeLock;
@Inject
AndroidWakeLockManagerImpl(Application app,
ScheduledExecutorService scheduledExecutorService) {
PowerManager powerManager = (PowerManager)
requireNonNull(app.getSystemService(POWER_SERVICE));
String tag = getWakeLockTag(app);
sharedWakeLock = new RenewableWakeLock(powerManager,
scheduledExecutorService, PARTIAL_WAKE_LOCK, tag,
LOCK_DURATION_MS, SAFETY_MARGIN_MS);
}
@Override
public AndroidWakeLock createWakeLock(String tag) {
return new AndroidWakeLockImpl(sharedWakeLock, tag);
}
@Override
public void runWakefully(Runnable r, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
r.run();
} finally {
wakeLock.release();
}
}
@Override
public void executeWakefully(Runnable r, Executor executor, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
executor.execute(() -> {
try {
r.run();
} finally {
// Release the wake lock if the task throws an exception
wakeLock.release();
}
});
} catch (Exception e) {
// Release the wake lock if the executor throws an exception when
// we submit the task (in which case the release() call above won't
// happen)
wakeLock.release();
throw e;
}
}
@Override
public void executeWakefully(Runnable r, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
new Thread(() -> {
try {
r.run();
} finally {
wakeLock.release();
}
}).start();
} catch (Exception e) {
wakeLock.release();
throw e;
}
}
private String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
if (isInstalled(pm, "com.huawei.powergenie")) {
return "LocationManagerService";
} else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
return "AudioIn";
}
return ctx.getPackageName();
}
private boolean isInstalled(PackageManager pm, String packageName) {
try {
pm.getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.system;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.util.concurrent.Executor;

View File

@@ -0,0 +1,130 @@
package org.briarproject.bramble.system;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@ThreadSafe
@NotNullByDefault
class RenewableWakeLock implements SharedWakeLock {
private static final Logger LOG =
getLogger(RenewableWakeLock.class.getName());
private final PowerManager powerManager;
private final ScheduledExecutorService scheduledExecutorService;
private final int levelAndFlags;
private final String tag;
private final long durationMs, safetyMarginMs;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private WakeLock wakeLock;
@GuardedBy("lock")
@Nullable
private Future<?> future;
@GuardedBy("lock")
private int refCount = 0;
@GuardedBy("lock")
private long acquired = 0;
RenewableWakeLock(PowerManager powerManager,
ScheduledExecutorService scheduledExecutorService,
int levelAndFlags,
String tag,
long durationMs,
long safetyMarginMs) {
this.powerManager = powerManager;
this.scheduledExecutorService = scheduledExecutorService;
this.levelAndFlags = levelAndFlags;
this.tag = tag;
this.durationMs = durationMs;
this.safetyMarginMs = safetyMarginMs;
}
@Override
public void acquire() {
synchronized (lock) {
refCount++;
if (refCount == 1) {
if (LOG.isLoggable(INFO)) {
LOG.info("Acquiring wake lock " + tag);
}
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
// We do our own reference counting so we can replace the lock
// TODO: Check whether using a ref-counted wake lock affects
// power management apps
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + safetyMarginMs);
future = scheduledExecutorService.schedule(this::renew,
durationMs, MILLISECONDS);
acquired = android.os.SystemClock.elapsedRealtime();
} else if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
}
}
private void renew() {
if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag);
synchronized (lock) {
if (wakeLock == null) {
LOG.info("Already released");
return;
}
if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
long now = android.os.SystemClock.elapsedRealtime();
long expiry = acquired + durationMs + safetyMarginMs;
if (now > expiry && LOG.isLoggable(WARNING)) {
LOG.warning("Wake lock expired " + (now - expiry) + " ms ago");
}
WakeLock oldWakeLock = wakeLock;
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + safetyMarginMs);
oldWakeLock.release();
future = scheduledExecutorService.schedule(this::renew, durationMs,
MILLISECONDS);
acquired = now;
}
}
@Override
public void release() {
synchronized (lock) {
refCount--;
if (refCount == 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Releasing wake lock " + tag);
}
requireNonNull(future).cancel(false);
future = null;
requireNonNull(wakeLock).release();
wakeLock = null;
acquired = 0;
} else if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
}
}
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SharedWakeLock {
/**
* Acquires the wake lock. This increments the wake lock's reference count,
* so unlike {@link AndroidWakeLock#acquire()} every call to this method
* must be followed by a balancing call to {@link #release()}.
*/
void acquire();
/**
* Releases the wake lock. This decrements the wake lock's reference count,
* so unlike {@link AndroidWakeLock#release()} every call to this method
* must follow a balancing call to {@link #acquire()}.
*/
void release();
}

View File

@@ -24,14 +24,9 @@ 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:obfs4proxy-android:0.0.14:obfs4proxy-android-0.0.14.jar:ad9b1ee4757b05867a19e993147bbb018bddd1f26ce3da746d5f037d5991a8c8',
'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.briarproject:tor-android:0.4.7.13:tor-android-0.4.7.13.jar:7852aab7d2298b80878c7719f34ce665725b494d673ecf2e6f9e697564638cc6',
'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',
@@ -42,12 +37,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.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0:kotlin-stdlib-jdk7-1.8.0.jar:4c889d1d9803f5f2eb6c1592a6b7e62369ac7660c9eee15aba16fec059163666',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',

View File

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

View File

@@ -11,7 +11,7 @@ apply from: '../dagger.gradle'
dependencies {
api project(':bramble-api')
api "org.briarproject:onionwrapper-core:$onionwrapper_version"
api 'org.briarproject:jtorctl:0.5'
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
//noinspection GradleDependency

View File

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

View File

@@ -40,9 +40,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
@@ -288,10 +288,8 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback {
private final TransportId id;
private final Object stateLock = new Object();
@GuardedBy("lock")
private State state = STARTING_STOPPING;
private final AtomicReference<State> state =
new AtomicReference<>(STARTING_STOPPING);
private Callback(TransportId id) {
this.id = id;
@@ -345,26 +343,22 @@ class PluginManagerImpl implements PluginManager, Service {
@Override
public void pluginStateChanged(State newState) {
synchronized (stateLock) {
if (newState != state) {
State oldState = state;
state = newState;
if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState
+ " to " + newState);
}
eventBus.broadcast(new TransportStateEvent(id, newState));
if (newState == ACTIVE) {
eventBus.broadcast(new TransportActiveEvent(id));
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
} else if (newState == DISABLED) {
// Broadcast an event even though the state hasn't changed,
// as the reasons for the plugin being disabled may have
// changed
eventBus.broadcast(new TransportStateEvent(id, newState));
State oldState = state.getAndSet(newState);
if (newState != oldState) {
if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState
+ " to " + newState);
}
eventBus.broadcast(new TransportStateEvent(id, newState));
if (newState == ACTIVE) {
eventBus.broadcast(new TransportActiveEvent(id));
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
} else if (newState == DISABLED) {
// Broadcast an event even though the state hasn't changed, as
// the reasons for the plugin being disabled may have changed
eventBus.broadcast(new TransportStateEvent(id, newState));
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.onionwrapper;
package org.briarproject.bramble.plugin.tor;
import javax.inject.Singleton;

View File

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

View File

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

View File

@@ -1,5 +1,8 @@
package org.briarproject.bramble.plugin.tor;
import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.battery.BatteryManager;
@@ -24,23 +27,30 @@ 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.nullsafety.InterfaceNotNullByDefault;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProvider.BridgeType;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.TorWrapper.HiddenServiceProperties;
import org.briarproject.onionwrapper.TorWrapper.Observer;
import org.briarproject.onionwrapper.TorWrapper.TorState;
import org.briarproject.nullsafety.ParametersNotNullByDefault;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.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;
@@ -53,9 +63,13 @@ import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
@@ -77,19 +91,36 @@ 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.onionwrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@InterfaceNotNullByDefault
class TorPlugin implements DuplexPlugin, EventListener {
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
"CIRC",
"ORCONN",
"STATUS_GENERAL",
"STATUS_CLIENT",
"HS_DESC",
"NOTICE",
"WARN",
"ERR"
};
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor;
@@ -98,79 +129,95 @@ class TorPlugin implements DuplexPlugin, EventListener {
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final CircumventionProvider circumventionProvider;
private final Clock clock;
private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final TorWrapper tor;
private final PluginCallback callback;
private final String architecture;
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final long maxLatency;
private final int maxIdleTime;
private final boolean canVerifyLetsEncryptCerts;
private final int socketTimeout;
private final File torDirectory;
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,
boolean canVerifyLetsEncryptCerts) {
File torDirectory,
int torSocksPort,
int torControlPort) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.torSocketFactory = torSocketFactory;
this.clock = clock;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto;
this.tor = tor;
this.callback = callback;
this.architecture = architecture;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
if (maxIdleTime > Integer.MAX_VALUE / 2) {
if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
} else {
socketTimeout = maxIdleTime * 2;
}
else socketTimeout = maxIdleTime * 2;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1);
tor.setObserver(new Observer() {
}
@Override
public void onState(TorState torState) {
State s = state.getState(torState);
if (s == ACTIVE) backoff.reset();
callback.pluginStateChanged(s);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor");
}
@Override
public void onBootstrapPercentage(int percentage) {
}
private File getLibEventFile() {
return new File(torDirectory, "libevent-2.1.7.dylib");
}
@Override
public void onHsDescriptorUpload(String onion) {
}
protected File getObfs4ExecutableFile() {
return new File(torDirectory, "obfs4proxy");
}
@Override
public void onClockSkewDetected(long skewSeconds) {
}
});
protected File getSnowflakeExecutableFile() {
return new File(torDirectory, "snowflake");
}
@Override
@@ -191,18 +238,89 @@ class TorPlugin implements DuplexPlugin, EventListener {
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory.");
throw new PluginException();
}
}
// Load the settings
settings = callback.getSettings();
// Start Tor
try {
tor.start();
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time
extract(getConfigInputStream(), configFile);
} catch (IOException e) {
throw new PluginException(e);
}
if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
try {
// Wait for the Tor process to start
waitForTorToStart(torProcess);
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
LOG.warning("Auth cookie not created");
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
throw new PluginException();
}
//noinspection BusyWait
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
} catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt();
throw new PluginException();
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", torControlPort);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER));
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped
String info = controlConnection.getInfo("status/bootstrap-phase");
if (info != null && info.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
state.setBootstrapped();
}
// Check whether Tor has already built a circuit
info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) {
LOG.info("Tor has already built a circuit");
state.setCircuitBuilt(true);
}
} catch (IOException e) {
throw new PluginException(e);
}
state.setStarted();
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
@@ -210,6 +328,132 @@ class TorPlugin implements DuplexPlugin, EventListener {
bind();
}
private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime();
}
private void installAssets() throws IOException {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
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();
File libEventFile = getLibEventFile();
extract(getExecutableInputStream("tor"), torFile);
extract(getExecutableInputStream("libevent-2.1.7.dylib"), libEventFile);
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
@@ -233,9 +477,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
return;
}
// Store the port number
int localPort = ss.getLocalPort();
String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put(PREF_TOR_PORT, String.valueOf(localPort));
s.put(PREF_TOR_PORT, localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
@@ -245,28 +489,48 @@ class TorPlugin implements DuplexPlugin, EventListener {
});
}
private void publishHiddenService(int localPort) {
if (!tor.isTorRunning()) return;
String privKey = settings.get(HS_PRIVATE_KEY_V3);
private void publishHiddenService(String port) {
if (!state.isTorRunning()) return;
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
publishV3HiddenService(port, privKey3);
}
private void publishV3HiddenService(String port, @Nullable String privKey) {
LOG.info("Creating v3 hidden service");
HiddenServiceProperties hsProps;
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response;
try {
hsProps = tor.publishHiddenService(localPort, 80, privKey);
// Use the control connection to set up the hidden service
if (privKey == null) {
response = controlConnection.addOnion("NEW:ED25519-V3",
portLines, null);
} else {
response = controlConnection.addOnion(privKey, portLines);
}
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!response.containsKey(HS_ADDRESS)) {
LOG.warning("Tor did not return a hidden service address");
return;
}
if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
LOG.warning("Tor did not return a private key");
return;
}
String onion3 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(hsProps.onion));
LOG.info("V3 hidden service " + scrubOnion(onion3));
}
if (privKey == null) {
// Publish the hidden service's onion hostname in transport props
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, hsProps.onion);
p.put(PROP_ONION_V3, onion3);
callback.mergeLocalProperties(p);
// Save the hidden service's private key for next time
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
callback.mergeSettings(s);
}
}
@@ -289,28 +553,50 @@ class TorPlugin implements DuplexPlugin, EventListener {
}
}
protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
if (bridgeTypes.isEmpty()) {
tor.disableBridges();
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
List<String> bridges = new ArrayList<>();
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
boolean letsEncrypt = canVerifyLetsEncryptCerts();
for (BridgeType bridgeType : bridgeTypes) {
bridges.addAll(circumventionProvider.getBridges(bridgeType,
countryCode, canVerifyLetsEncryptCerts));
conf.addAll(circumventionProvider
.getBridges(bridgeType, countryCode, letsEncrypt));
}
tor.enableBridges(bridges);
controlConnection.setConf(conf);
}
}
/**
* Returns true if this device can verify Let's Encrypt certificates signed
* with the IdentTrust DST Root X3 certificate, which expired at the end of
* September 2021.
*/
protected boolean canVerifyLetsEncryptCerts() {
return true;
}
@Override
public void stop() {
ServerSocket ss = state.setStopped();
tryToClose(ss, LOG, WARNING);
try {
tor.stop();
} catch (IOException e) {
logException(LOG, WARNING, e);
if (controlSocket != null && controlConnection != null) {
try {
LOG.info("Stopping Tor");
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
}
@@ -421,7 +707,6 @@ class TorPlugin implements DuplexPlugin, EventListener {
TransportProperties remoteProperties = new TransportProperties();
remoteProperties.put(PROP_ONION_V3, remoteOnion);
try {
@SuppressWarnings("resource")
ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("127.0.0.1", 0));
int port = ss.getLocalPort();
@@ -438,7 +723,9 @@ class TorPlugin implements DuplexPlugin, EventListener {
LOG.info("Rendezvous server socket closed");
}
});
tor.publishHiddenService(port, 80, blob);
Map<Integer, String> portLines =
singletonMap(80, "127.0.0.1:" + port);
controlConnection.addOnion(blob, portLines);
return new RendezvousEndpoint() {
@Override
@@ -448,11 +735,8 @@ class TorPlugin implements DuplexPlugin, EventListener {
@Override
public void close() throws IOException {
try {
tor.removeHiddenService(localOnion);
} finally {
tryToClose(ss, LOG, WARNING);
}
controlConnection.delOnion(localOnion);
tryToClose(ss, LOG, WARNING);
}
};
} catch (IOException e) {
@@ -461,6 +745,121 @@ class TorPlugin implements DuplexPlugin, EventListener {
}
}
@Override
public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@Override
public void streamStatus(String status, String id, String target) {
}
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
}
@Override
public void bandwidthUsed(long read, long written) {
}
@Override
public void newDescriptors(List<String> orList) {
}
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
}
@Override
public void unrecognized(String type, String msg) {
if (type.equals("STATUS_CLIENT")) {
handleClientStatus(removeSeverity(msg));
} else if (type.equals("STATUS_GENERAL")) {
handleGeneralStatus(removeSeverity(msg));
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
String[] parts = msg.split(" ");
if (parts.length < 2) {
LOG.warning("Failed to parse HS_DESC UPLOADED event");
} else if (LOG.isLoggable(INFO)) {
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
}
}
}
@Override
public void controlConnectionClosed() {
if (state.isTorRunning()) {
// 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) {
@@ -483,7 +882,7 @@ class TorPlugin implements DuplexPlugin, EventListener {
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!tor.isTorRunning()) return;
if (!state.isTorRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
@@ -567,22 +966,41 @@ class TorPlugin implements DuplexPlugin, EventListener {
try {
if (enableNetwork) {
enableBridges(bridgeTypes, country);
tor.enableConnectionPadding(enableConnectionPadding);
tor.enableIpv6(ipv6Only);
enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only);
}
tor.enableNetwork(enableNetwork);
enableNetwork(enableNetwork);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
});
}
private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
private void enableIpv6(boolean enable) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
}
@ThreadSafe
@NotNullByDefault
private class PluginState {
protected class PluginState {
@GuardedBy("this")
private boolean settingsChecked = false;
private boolean started = false,
stopped = false,
networkInitialised = false,
networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
@GuardedBy("this")
private int reasonsDisabled = 0;
@@ -591,13 +1009,84 @@ class TorPlugin implements DuplexPlugin, EventListener {
@Nullable
private ServerSocket serverSocket = null;
@GuardedBy("this")
private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@Nullable
private synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true;
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
/**
* Sets the `circuitBuilt` flag and returns true if the flag has
* changed.
*/
private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built;
callback.pluginStateChanged(getState());
return true; // Changed
}
/**
* Sets the `networkEnabled` flag and returns true if the flag has
* changed.
*/
private synchronized boolean enableNetwork(boolean enable) {
boolean wasInitialised = networkInitialised;
boolean wasEnabled = networkEnabled;
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) {
callback.pluginStateChanged(getState());
}
return enable != wasEnabled;
}
/**
* Sets the `paddingEnabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableConnectionPadding(boolean enable) {
if (enable == paddingEnabled) return false; // Unchanged
paddingEnabled = enable;
return true; // Changed
}
/**
* Sets the `ipv6Enabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableIpv6(boolean enable) {
if (enable == ipv6Enabled) return false; // Unchanged
ipv6Enabled = enable;
return true; // Changed
}
private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked;
settingsChecked = true;
@@ -610,7 +1099,7 @@ class TorPlugin implements DuplexPlugin, EventListener {
// Doesn't affect getState()
private synchronized boolean setServerSocket(ServerSocket ss) {
if (serverSocket != null || !tor.isTorRunning()) return false;
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
@@ -620,22 +1109,57 @@ class TorPlugin implements DuplexPlugin, EventListener {
if (serverSocket == ss) serverSocket = null;
}
private synchronized State getState() {
return getState(tor.getTorState());
/**
* Sets the list of bridge types being used and returns true if the
* list has changed. The list is empty if bridges are disabled.
* Doesn't affect getState().
*/
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
}
private synchronized State getState(TorState torState) {
if (torState == TorState.STARTING_STOPPING || !settingsChecked) {
private synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
if (torState == TorState.CONNECTING) return ENABLING;
if (torState == TorState.CONNECTED) return ACTIVE;
return INACTIVE;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
? ACTIVE : ENABLING;
}
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
}
private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected++;
logOrConnections();
if (oldConnected == 0) callback.pluginStateChanged(getState());
}
private synchronized void onOrConnectionClosed() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected--;
if (orConnectionsConnected < 0) {
LOG.warning("Count was zero before connection closed");
orConnectionsConnected = 0;
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
callback.pluginStateChanged(getState());
}
}
@GuardedBy("this")
private void logOrConnections() {
if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -18,9 +17,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;
@@ -46,12 +45,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
protected final Executor ioExecutor, eventExecutor, wakefulIoExecutor;
protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
protected final Clock clock;
@@ -61,13 +61,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
protected final int torControlPort;
TorPluginFactory(@IoExecutor Executor ioExecutor,
@EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@@ -76,13 +76,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
this.ioExecutor = ioExecutor;
this.eventExecutor = eventExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.clock = clock;

View File

@@ -0,0 +1,35 @@
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

View File

@@ -0,0 +1,4 @@
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

View File

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

View File

@@ -25,6 +25,11 @@ public class TestFeatureFlagModule {
return true;
}
@Override
public boolean shouldEnableMailbox() {
return true;
}
@Override
public boolean shouldEnablePrivateGroupsInCore() {
return true;

View File

@@ -37,7 +37,6 @@ 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',

View File

@@ -11,10 +11,13 @@ dependencies {
implementation project(':bramble-core')
implementation fileTree(dir: 'libs', include: '*.jar')
def jna_version = '4.5.2'
def jna_version = '5.10.0'
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
implementation "org.briarproject:onionwrapper-java:$onionwrapper_version"
testImplementation "org.briarproject:tor-linux:$tor_version"
testImplementation "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
testImplementation "org.briarproject:snowflake-linux:$snowflake_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -24,6 +27,9 @@ 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) {

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
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.load("c", CLibrary.class);
int getpid();
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -14,11 +13,9 @@ 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;
@@ -30,6 +27,7 @@ import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
@Immutable
@NotNullByDefault
@@ -37,13 +35,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,
@@ -51,8 +49,8 @@ public class UnixTorPluginFactory extends TorPluginFactory {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@@ -60,15 +58,18 @@ public class UnixTorPluginFactory extends TorPluginFactory {
@Nullable
@Override
String getArchitectureForTorBinary() {
if (!isLinux()) return null;
if (!isLinux() && !isMac()) return null;
String arch = System.getProperty("os.arch");
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";
if (isLinux()) {
if (arch.equals("amd64")) return "x86_64";
else if (arch.equals("aarch64")) return "aarch64";
else if (arch.equals("arm")) return "armhf";
} else if (isMac()) {
return "any";
}
return null;
}
@@ -76,11 +77,11 @@ public class UnixTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
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);
return new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}
}

View File

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

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -14,11 +13,9 @@ 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;
@@ -37,13 +34,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,
@@ -51,8 +48,8 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@@ -73,11 +70,11 @@ public class WindowsTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
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);
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}
}

View File

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

View File

@@ -0,0 +1,57 @@
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) {
}
}

View File

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

View File

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

View File

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

View File

@@ -15,19 +15,20 @@ 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',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'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-platform:5.10.0:jna-platform-5.10.0.jar:1f71afd977051bf0109ef5e3767d4e2afd777be894d89788cc0f38ad68f6a16f',
'net.java.dev.jna:jna:5.10.0:jna-5.10.0.jar:e335c10679f743207d822c5f7948e930319835492575a9dba6b94f8a3b96fcc8',
'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: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.briarproject:obfs4proxy-linux:0.0.14:obfs4proxy-linux-0.0.14.jar:6391d323d45a279362236c7c62e21b903d07d4f31f5e0c8d49d009769b720cc6',
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
'org.briarproject:tor-linux:0.4.7.13:tor-linux-0.4.7.13.jar:9819ee973cbcdc133f7d04aef9d4b957a35087627a790e532142d15412a9636f',
'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',

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
versionCode 10501
versionName "1.5.1"
versionCode 10423
versionName "1.4.23"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\""
@@ -116,6 +116,7 @@ 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'

View File

@@ -1,6 +1,5 @@
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;
@@ -26,10 +25,12 @@ 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;
@@ -83,7 +84,6 @@ 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;

View File

@@ -33,6 +33,7 @@ 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;
@@ -211,7 +212,7 @@ public class AppModule {
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
List<SimplexPluginFactory> simplex = new ArrayList<>();
simplex.add(mailbox);
if (featureFlags.shouldEnableMailbox()) simplex.add(mailbox);
if (SDK_INT >= 19) simplex.add(drive);
return simplex;
}
@@ -352,6 +353,11 @@ public class AppModule {
return true;
}
@Override
public boolean shouldEnableMailbox() {
return BuildConfig.DEBUG;
}
@Override
public boolean shouldEnablePrivateGroupsInCore() {
return true;

View File

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

View File

@@ -5,7 +5,7 @@ import android.transition.Transition;
import android.view.Window;
import android.widget.CheckBox;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;

View File

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

View File

@@ -83,11 +83,15 @@ public class SettingsFragment extends PreferenceFragmentCompat {
Preference prefMailbox =
requireNonNull(findPreference(PREF_KEY_MAILBOX));
prefMailbox.setOnPreferenceClickListener(preference -> {
Intent i = new Intent(requireContext(), MailboxActivity.class);
startActivity(i);
return true;
});
if (viewModel.shouldEnableMailbox()) {
prefMailbox.setOnPreferenceClickListener(preference -> {
Intent i = new Intent(requireContext(), MailboxActivity.class);
startActivity(i);
return true;
});
} else {
prefMailbox.setVisible(false);
}
Preference prefFeedback =
requireNonNull(findPreference(PREF_KEY_FEEDBACK));

View File

@@ -24,6 +24,7 @@ import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
@@ -33,7 +34,6 @@ 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,6 +161,10 @@ class SettingsViewModel extends DbViewModel implements EventListener {
return featureFlags.shouldEnableProfilePictures();
}
boolean shouldEnableMailbox() {
return featureFlags.shouldEnableMailbox();
}
private void loadOwnIdentityInfo() {
runOnDbThread(() -> {
try {

View File

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

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.splash;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;

View File

@@ -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Ако искате да свържете Пощенска кутия използвайте Настройки &gt; Пощенска кутия от менюто.</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,10 +602,6 @@
<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>

View File

@@ -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 „Uzamčení aplikací“ nastaven na „Uzamčeno“, nebude moci běžet na pozadí.</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_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 v okolí</string>
<string name="add_contact_title">Přidat kontakt nablízku</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 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="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="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 mohly spojit.</string>
<string name="introduction_menu_item">Představit</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_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">Představit</string>
<string name="introduction_button">Vytvořit představení</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í %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_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_declined_auto">Představení %1$s bylo automaticky zamítnuto.</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>
<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>
<!--Connect via Bluetooth-->
<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="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 pobž, 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 zakladatel 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 tvůrce 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">Znovu-zveřejnit</string>
<string name="blogs_reblog_button">Reblog</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">Import kanálu ze souboru</string>
<string name="blogs_rss_feeds_import_title">Importovat kanál 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 při načtení vašich kanálů. Zkuste to prosím později.</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>
<!--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">Motiv</string>
<string name="pref_theme_title">Vzhled</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">Spojení</string>
<string name="network_settings_title">Connections</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 v okolí a to 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 blízko 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>

View File

@@ -244,7 +244,6 @@
<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>
@@ -454,10 +453,6 @@
<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>
@@ -514,9 +509,7 @@
<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>

View File

@@ -243,7 +243,6 @@
<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>

View File

@@ -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,14 +19,13 @@
<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_dialog_body_new">1. 最近使ったアプリの一覧(アプリスイッチャーともいう)を開いて下さい。\n\n2. Briarの名前の隣に南京錠の小さな画像があれば、何もする必要がありません。\n\n3. そこに南京錠がなければ、南京錠ボタンが現れるまでBriarの画像を押し続け、そして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_warning_dozed_1">Briarはバックグラウンドで実行できませんでした</string>
<!--Login-->
<string name="enter_password">パスワード</string>
@@ -39,31 +38,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.-->
@@ -74,41 +73,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>
@@ -127,8 +126,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>
@@ -138,14 +137,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-->
@@ -154,14 +153,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."-->
@@ -181,14 +180,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>
@@ -196,9 +195,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>
@@ -207,9 +206,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>
@@ -217,65 +216,64 @@
<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メールボックスに関連付けたいならば、設定を選択してください&gt; Briarメニューからメールボックス</string>
<string name="mailbox_qr_code_for_contact">読み取ったQRコードは、Briarメールボックスから生じるものです。\n\nメールボックスに関連付けたいならば、設定を選択してください&gt; 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">あなた自身のリンクを開きました。追加したい連絡先のリンクを使用してください!</string>
<string name="unsupported_link">このリンクはBriarの新しいバージョンからのものです。 最新版にアップグレードしてから、もう一度試してください。</string>
<string name="intent_own_link">あなたのBriar自身を指すリンクを開きました。 別の追加したい連絡先を使用してください!</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>
@@ -288,8 +286,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-->
@@ -300,53 +298,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>
@@ -354,14 +352,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>
@@ -369,39 +367,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>
@@ -413,8 +411,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>
@@ -423,13 +421,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>
@@ -442,30 +440,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-->
@@ -481,7 +479,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-->
@@ -495,14 +493,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>
@@ -514,7 +512,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>
@@ -524,25 +522,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>
@@ -558,9 +556,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>
@@ -570,7 +568,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>
@@ -578,11 +576,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>
@@ -597,7 +595,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>
@@ -619,11 +617,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>
@@ -652,9 +650,9 @@
<string name="mailbox_error_wizard_info2">端末にアクセスしたら、この画面に戻って来てください。</string>
<string name="mailbox_error_wizard_info3">以下のボタンを使用してメールボックスをリンク解除してください。\n\n古いメールボックスをリンク解除した後、いつでも新しいメールボックスをセットアップできます。</string>
<!--About-->
<string name="about_title">バージョン情報</string>
<string name="about_title">Tor Project について</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>
@@ -665,12 +663,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>
@@ -717,20 +715,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に触れても反応しません。\n\n以下のアプリが上に描画されている可能性があります:\n\n%1$s</string>
<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_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>
@@ -750,7 +748,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を使用して、インターネット接続なしで、近くの誰かとこのアプリを共有します。
@@ -819,10 +817,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>
@@ -832,10 +830,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.-->

View File

@@ -256,8 +256,6 @@
<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>
@@ -635,7 +633,6 @@
<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>
@@ -775,12 +772,10 @@ 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>

View File

@@ -120,6 +120,7 @@ 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',
@@ -135,11 +136,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.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
'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-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.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
'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',

View File

@@ -20,6 +20,9 @@ configurations {
linux {
extendsFrom runtimeClasspath
}
macos {
extendsFrom runtimeClasspath
}
}
sourceCompatibility = 1.8
@@ -38,6 +41,10 @@ dependencies {
windows "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
windows "org.briarproject:snowflake-windows:$snowflake_version"
macos "org.briarproject:tor-macos-torbrowser:$tor_version"
macos "org.briarproject:obfs4proxy-macos-torbrowser:$obfs4proxy_version"
macos "org.briarproject:snowflake-macos-torbrowser:$snowflake_version"
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10'
implementation 'io.javalin:javalin:3.5.0'
implementation 'org.slf4j:slf4j-simple:1.7.30'
@@ -45,7 +52,6 @@ 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"

View File

@@ -116,6 +116,7 @@ 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

View File

@@ -36,22 +36,19 @@ 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:obfs4proxy-linux:0.0.14:obfs4proxy-linux-0.0.14.jar:6391d323d45a279362236c7c62e21b903d07d4f31f5e0c8d49d009769b720cc6',
'org.briarproject:obfs4proxy-macos:0.0.14:obfs4proxy-macos-0.0.14.jar:739daa907ce00957d0faf6b71a47dd0b462f509b2a6fbdfbe2f23a60a8ee9b3e',
'org.briarproject:obfs4proxy-windows:0.0.14:obfs4proxy-windows-0.0.14.jar:801d48525f52583a470a1671026b87992176d4432b299774989387cb87bc8ba3',
'org.briarproject:snowflake-linux:2.5.1:snowflake-linux-2.5.1.jar:edc807dcb7758365970d95525e4749349a27f462d0e2df6505ad1ca65fb296d2',
'org.briarproject:snowflake-macos:2.5.1:snowflake-macos-2.5.1.jar:d71fe386e6635686dd640f14733111195602c133c6b5a22adfea8bb9b11d761a',
'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',
'org.briarproject:tor-windows:0.4.7.13-2:tor-windows-0.4.7.13-2.jar:3a0aa01ed3103cac0c22a91a6f227ab99f7d32ea970ea41558eece484ab49e88',
'org.briarproject:tor-linux:0.4.7.13:tor-linux-0.4.7.13.jar:9819ee973cbcdc133f7d04aef9d4b957a35087627a790e532142d15412a9636f',
'org.briarproject:tor-macos:0.4.7.13:tor-macos-0.4.7.13.jar:6cbe4e3f64b33be807626c3772b1a8edda415ad29459f71cffc58e2bdf93965c',
'org.briarproject:tor-windows:0.4.7.13:tor-windows-0.4.7.13.jar:853d2769665614e26703cbe02e43b218b064c04a0bcd120fdc459cda45bd2606',
'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.eclipse.jetty.websocket:websocket-api:9.4.20.v20190813:websocket-api-9.4.20.v20190813.jar:779a29060cc17bdeeeba147efc884ebff972cfff93dad2d37b11c93f95d4f67b',

View File

@@ -3,8 +3,10 @@
allprojects {
repositories {
mavenCentral()
mavenLocal()
google()
maven { url "https://jitpack.io" }
maven { url "https://mvntmp.mobanisto.de" }
}
afterEvaluate {
tasks.withType(Test) {
@@ -18,6 +20,7 @@ allprojects {
buildscript {
repositories {
mavenLocal()
google()
maven {
url 'https://plugins.gradle.org/m2/'
@@ -30,15 +33,14 @@ buildscript {
// okhttp 3.12.x is supported until end of 2021, newer versions need minSdk 21
okhttp_version = "3.12.13"
jackson_version = "2.13.4"
tor_version = "0.4.7.13-2"
obfs4proxy_version = "0.0.14-tor2"
tor_version = "0.4.7.13"
obfs4proxy_version = "0.0.14"
snowflake_version = "2.5.1"
jsoup_version = '1.15.3'
bouncy_castle_version = '1.71'
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'