mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
34 Commits
release-1.
...
bluetooth-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb98cb2f23 | ||
|
|
aa331981cc | ||
|
|
e9a6faad2d | ||
|
|
5025cf1e40 | ||
|
|
834342fd3a | ||
|
|
3028b236e1 | ||
|
|
254422bc02 | ||
|
|
c7949d6e00 | ||
|
|
0187264da7 | ||
|
|
85a18cf53f | ||
|
|
3181b695df | ||
|
|
b2ac210586 | ||
|
|
d20340416d | ||
|
|
9da871718c | ||
|
|
3793cb841b | ||
|
|
c6b88b51f0 | ||
|
|
2f00215a44 | ||
|
|
183f0c5f31 | ||
|
|
34c5aaae0a | ||
|
|
5531355ebd | ||
|
|
b9e607744a | ||
|
|
def62bce5a | ||
|
|
9dae3d191a | ||
|
|
20422edf78 | ||
|
|
f8bc5f08bf | ||
|
|
9434495d70 | ||
|
|
bf9e91fcf5 | ||
|
|
d9d86206a6 | ||
|
|
b410b8efcc | ||
|
|
39aa2d96b3 | ||
|
|
21dae824a6 | ||
|
|
cfdbd29cb4 | ||
|
|
4df335ebd3 | ||
|
|
682bee1486 |
@@ -1,15 +1,15 @@
|
||||
image: registry.gitlab.com/fdroid/ci-images-base:latest
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
|
||||
before_script:
|
||||
- set -e
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
image: briar/ci-image-android:latest
|
||||
|
||||
test:
|
||||
before_script:
|
||||
- set -e
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
|
||||
script:
|
||||
- ./gradlew --no-daemon animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon test
|
||||
@@ -19,12 +19,13 @@ test:
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||
|
||||
|
||||
test_reproducible:
|
||||
image: briar/reproducer:latest
|
||||
|
||||
script:
|
||||
- cd .. && mv briar /opt/briar-reproducer/
|
||||
- cd /opt/briar-reproducer
|
||||
- ./reproduce.py ${CI_COMMIT_REF_NAME}
|
||||
|
||||
only:
|
||||
- tags
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'witness'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
@@ -14,8 +8,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10004
|
||||
versionName "1.0.4"
|
||||
versionCode 10005
|
||||
versionName "1.0.5"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
@@ -25,9 +19,14 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
tor
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
implementation 'org.briarproject:jtorctl:0.3'
|
||||
tor 'org.briarproject:tor-android:0.2.9.15@zip'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
@@ -92,6 +91,8 @@ dependencyVerification {
|
||||
'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
||||
'org.briarproject:tor-android:0.2.9.15:tor-android-0.2.9.15.zip:34a6474ee219ffa52e0f3393e917dda6ed03d320b02247d4fa5075aa4094ee6d',
|
||||
'org.codehaus.groovy:groovy-all:2.4.12:groovy-all-2.4.12.jar:6a56af4bd48903d56bec62821876cadefafd007360cc6bd0d8f7aa8d72b38be4',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
|
||||
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
|
||||
@@ -112,81 +113,9 @@ dependencyVerification {
|
||||
]
|
||||
}
|
||||
|
||||
ext.torBinaryDir = 'src/main/res/raw'
|
||||
ext.torVersion = '0.2.9.14'
|
||||
ext.geoipVersion = '2017-11-06'
|
||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||
|
||||
def torBinaries = [
|
||||
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||
]
|
||||
|
||||
def verifyOrDeleteBinary(name, chksum, alreadyVerified) {
|
||||
return tasks.create("verifyOrDeleteBinary${name}", VerifyOrDelete) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
result alreadyVerified
|
||||
onlyIf {
|
||||
src.exists()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def downloadBinary(name, chksum, alreadyVerified) {
|
||||
return tasks.create([
|
||||
name: "downloadBinary${name}",
|
||||
type: Download,
|
||||
dependsOn: verifyOrDeleteBinary(name, chksum, alreadyVerified)]) {
|
||||
src "${torDownloadUrl}${name}.zip"
|
||||
.replace('tor_', "tor-${torVersion}-")
|
||||
.replace('geoip', "geoip-${geoipVersion}")
|
||||
.replaceAll('_', '-')
|
||||
dest "${torBinaryDir}/${name}.zip"
|
||||
onlyIf {
|
||||
!dest.exists()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def verifyBinary(name, chksum) {
|
||||
boolean[] alreadyVerified = [false]
|
||||
return tasks.create([
|
||||
name : "verifyBinary${name}",
|
||||
type : Verify,
|
||||
dependsOn: downloadBinary(name, chksum, alreadyVerified)]) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
onlyIf {
|
||||
!alreadyVerified[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
torBinaries.every { name, checksum ->
|
||||
preBuild.dependsOn.add(verifyBinary(name, checksum))
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyOrDelete extends Verify {
|
||||
|
||||
boolean[] result
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
void verify() throws IOException, NoSuchAlgorithmException {
|
||||
try {
|
||||
super.verify()
|
||||
result[0] = true
|
||||
} catch (Exception e) {
|
||||
println "${src} failed verification - deleting"
|
||||
src.delete()
|
||||
}
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into 'src/main/res/raw'
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,10 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.plugin.AndroidPluginModule;
|
||||
import org.briarproject.bramble.system.AndroidSystemModule;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
@Module(includes = {
|
||||
AndroidPluginModule.class,
|
||||
AndroidSystemModule.class
|
||||
})
|
||||
public class BrambleAndroidModule {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class AndroidPluginModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
AndroidExecutor androidExecutor, SecureRandom random,
|
||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||
Application app, LocationUtils locationUtils, EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, eventBus, torSocketFactory,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
scheduler, backoffFactory, appContext);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return duplex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
return pluginConfig;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,6 @@ import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -334,7 +333,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getConfigInputStream() throws IOException {
|
||||
private InputStream getConfigInputStream() {
|
||||
int resId = getResourceId("torrc");
|
||||
return appContext.getResources().openRawResource(resId);
|
||||
}
|
||||
@@ -499,7 +498,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws PluginException {
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
if (networkStateReceiver != null)
|
||||
@@ -533,20 +532,16 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
public void poll(Map<ContactId, TransportProperties> contacts) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
|
||||
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
||||
connectAndCallBack(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(ContactId c, TransportProperties p) {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
@@ -556,13 +551,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (!isRunning()) return null;
|
||||
return createConnection(callback.getRemoteProperties(c));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
String onion = p.get(PROP_ONION);
|
||||
if (StringUtils.isNullOrEmpty(onion)) return null;
|
||||
if (!ONION.matcher(onion).matches()) {
|
||||
|
||||
@@ -8,16 +8,13 @@ import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
public class AndroidUtils {
|
||||
|
||||
@@ -61,77 +58,28 @@ public class AndroidUtils {
|
||||
&& !address.equals(FAKE_BLUETOOTH_ADDRESS);
|
||||
}
|
||||
|
||||
public static void logDataDirContents(Context ctx) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Contents of data directory:");
|
||||
logFileOrDir(new File(ctx.getApplicationInfo().dataDir));
|
||||
}
|
||||
}
|
||||
|
||||
private static void logFileOrDir(File f) {
|
||||
LOG.info(f.getAbsolutePath() + " " + f.length());
|
||||
if (f.isDirectory()) {
|
||||
File[] children = f.listFiles();
|
||||
if (children == null) {
|
||||
LOG.info("Could not list files in " + f.getAbsolutePath());
|
||||
} else {
|
||||
for (File child : children) logFileOrDir(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static File getSharedPrefsFile(Context ctx, String name) {
|
||||
File dataDir = new File(ctx.getApplicationInfo().dataDir);
|
||||
File prefsDir = new File(dataDir, "shared_prefs");
|
||||
return new File(prefsDir, name + ".xml");
|
||||
}
|
||||
|
||||
public static void logFileContents(File f) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Contents of " + f.getAbsolutePath() + ":");
|
||||
try {
|
||||
Scanner s = new Scanner(f);
|
||||
while (s.hasNextLine()) LOG.info(s.nextLine());
|
||||
s.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.info(f.getAbsolutePath() + " not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public static void deleteAppData(Context ctx, SharedPreferences... clear) {
|
||||
// Clear and commit shared preferences
|
||||
for (SharedPreferences prefs : clear) {
|
||||
boolean cleared = prefs.edit().clear().commit();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (cleared) LOG.info("Cleared shared preferences");
|
||||
else LOG.info("Could not clear shared preferences");
|
||||
}
|
||||
if (!prefs.edit().clear().commit())
|
||||
LOG.warning("Could not clear shared preferences");
|
||||
}
|
||||
// Delete files, except lib and shared_prefs directories
|
||||
File dataDir = new File(ctx.getApplicationInfo().dataDir);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Deleting app data from " + dataDir.getAbsolutePath());
|
||||
File[] children = dataDir.listFiles();
|
||||
if (children != null) {
|
||||
if (children == null) {
|
||||
LOG.warning("Could not list files in app data dir");
|
||||
} else {
|
||||
for (File child : children) {
|
||||
String name = child.getName();
|
||||
if (!name.equals("lib") && !name.equals("shared_prefs")) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Deleting " + child.getAbsolutePath());
|
||||
IoUtils.deleteFileOrDir(child);
|
||||
}
|
||||
}
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Could not list files in " + dataDir.getAbsolutePath());
|
||||
}
|
||||
// Recreate the cache dir as some OpenGL drivers expect it to exist
|
||||
boolean recreated = new File(dataDir, "cache").mkdir();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (recreated) LOG.info("Recreated cache dir");
|
||||
else LOG.info("Could not recreate cache dir");
|
||||
}
|
||||
if (!new File(dataDir, "cache").mkdir())
|
||||
LOG.warning("Could not recreate cache dir");
|
||||
}
|
||||
|
||||
public static File getReportDir(Context ctx) {
|
||||
|
||||
@@ -14,6 +14,8 @@ public interface DatabaseConfig {
|
||||
|
||||
File getDatabaseDirectory();
|
||||
|
||||
File getDatabaseKeyDirectory();
|
||||
|
||||
void setEncryptionKey(SecretKey key);
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
public interface FileConstants {
|
||||
|
||||
String PROP_PATH = "path";
|
||||
}
|
||||
@@ -2,8 +2,9 @@ package org.briarproject.bramble.api.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface Plugin {
|
||||
@@ -39,21 +40,19 @@ public interface Plugin {
|
||||
boolean isRunning();
|
||||
|
||||
/**
|
||||
* Returns true if the plugin's {@link #poll(Collection)} method should be
|
||||
* called periodically to attempt to establish connections.
|
||||
* Returns true if the plugin should be polled periodically to attempt to
|
||||
* establish connections.
|
||||
*/
|
||||
boolean shouldPoll();
|
||||
|
||||
/**
|
||||
* Returns the desired interval in milliseconds between calls to the
|
||||
* plugin's {@link #poll(Collection)} method.
|
||||
* Returns the desired interval in milliseconds between polling attempts.
|
||||
*/
|
||||
int getPollingInterval();
|
||||
|
||||
/**
|
||||
* Attempts to establish connections to contacts, passing any created
|
||||
* connections to the callback. To avoid creating redundant connections,
|
||||
* the plugin may exclude the given contacts from polling.
|
||||
* Attempts to establish connections to the given contacts, passing any
|
||||
* created connections to the callback.
|
||||
*/
|
||||
void poll(Collection<ContactId> connected);
|
||||
void poll(Map<ContactId, TransportProperties> contacts);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An interface through which a transport plugin interacts with the rest of
|
||||
* the application.
|
||||
@@ -25,17 +22,7 @@ public interface PluginCallback {
|
||||
TransportProperties getLocalProperties();
|
||||
|
||||
/**
|
||||
* Returns the plugin's remote transport properties.
|
||||
*/
|
||||
Map<ContactId, TransportProperties> getRemoteProperties();
|
||||
|
||||
/**
|
||||
* Returns the plugin's remote transport properties for the given contact.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(ContactId c);
|
||||
|
||||
/**
|
||||
* Merges the given settings with the namespaced settings
|
||||
* Merges the given settings with the plugin's settings
|
||||
*/
|
||||
void mergeSettings(Settings s);
|
||||
|
||||
@@ -45,34 +32,12 @@ public interface PluginCallback {
|
||||
void mergeLocalProperties(TransportProperties p);
|
||||
|
||||
/**
|
||||
* Presents the user with a choice among two or more named options and
|
||||
* returns the user's response. The message may consist of a translatable
|
||||
* format string and arguments.
|
||||
*
|
||||
* @return an index into the array of options indicating the user's choice,
|
||||
* or -1 if the user cancelled the choice.
|
||||
*/
|
||||
int showChoice(String[] options, String... message);
|
||||
|
||||
/**
|
||||
* Asks the user to confirm an action and returns the user's response. The
|
||||
* message may consist of a translatable format string and arguments.
|
||||
*/
|
||||
boolean showConfirmationMessage(String... message);
|
||||
|
||||
/**
|
||||
* Shows a message to the user. The message may consist of a translatable
|
||||
* format string and arguments.
|
||||
*/
|
||||
void showMessage(String... message);
|
||||
|
||||
/**
|
||||
* Signal that the transport got enabled.
|
||||
* Signals that the transport is enabled.
|
||||
*/
|
||||
void transportEnabled();
|
||||
|
||||
/**
|
||||
* Signal that the transport got disabled.
|
||||
* Signals that the transport is disabled.
|
||||
*/
|
||||
void transportDisabled();
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ public interface PluginConfig {
|
||||
Collection<DuplexPluginFactory> getDuplexFactories();
|
||||
|
||||
Collection<SimplexPluginFactory> getSimplexFactories();
|
||||
|
||||
boolean shouldPoll();
|
||||
}
|
||||
|
||||
@@ -22,11 +22,6 @@ public interface TransportConnectionWriter {
|
||||
*/
|
||||
int getMaxIdleTime();
|
||||
|
||||
/**
|
||||
* Returns the capacity of the transport connection in bytes.
|
||||
*/
|
||||
long getCapacity();
|
||||
|
||||
/**
|
||||
* Returns an output stream for writing to the transport connection.
|
||||
*/
|
||||
|
||||
@@ -71,11 +71,6 @@ public abstract class AbstractDuplexTransportConnection
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCapacity() {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return AbstractDuplexTransportConnection.this.getOutputStream();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.api.plugin.duplex;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -15,12 +15,11 @@ import javax.annotation.Nullable;
|
||||
public interface DuplexPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Attempts to create and return a connection to the given contact using
|
||||
* the current transport and configuration properties. Returns null if a
|
||||
* connection cannot be created.
|
||||
* Attempts to create and return a connection using the given transport
|
||||
* properties. Returns null if a connection cannot be created.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createConnection(ContactId c);
|
||||
DuplexTransportConnection createConnection(TransportProperties p);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports short-range key agreement.
|
||||
|
||||
@@ -5,7 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
|
||||
/**
|
||||
* An interface for handling connections created by a duplex transport plugin.
|
||||
* An interface through which a duplex plugin interacts with the rest of the
|
||||
* application.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface DuplexPluginCallback extends PluginCallback {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.bramble.api.plugin.simplex;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -15,18 +15,16 @@ import javax.annotation.Nullable;
|
||||
public interface SimplexPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Attempts to create and return a reader for the given contact using the
|
||||
* current transport and configuration properties. Returns null if a reader
|
||||
* cannot be created.
|
||||
* Attempts to create and return a reader for the given transport
|
||||
* properties. Returns null if a reader cannot be created.
|
||||
*/
|
||||
@Nullable
|
||||
TransportConnectionReader createReader(ContactId c);
|
||||
TransportConnectionReader createReader(TransportProperties p);
|
||||
|
||||
/**
|
||||
* Attempts to create and return a writer for the given contact using the
|
||||
* current transport and configuration properties. Returns null if a writer
|
||||
* cannot be created.
|
||||
* Attempts to create and return a writer for the given transport
|
||||
* properties. Returns null if a writer cannot be created.
|
||||
*/
|
||||
@Nullable
|
||||
TransportConnectionWriter createWriter(ContactId c);
|
||||
TransportConnectionWriter createWriter(TransportProperties p);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
|
||||
/**
|
||||
* An interface for handling readers and writers created by a simplex transport
|
||||
* plugin.
|
||||
* An interface through which a simplex plugin interacts with the rest of the
|
||||
* application.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface SimplexPluginCallback extends PluginCallback {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.briarproject.bramble.api.ui;
|
||||
|
||||
public interface UiCallback {
|
||||
|
||||
/**
|
||||
* Presents the user with a choice among two or more named options and
|
||||
* returns the user's response. The message may consist of a translatable
|
||||
* format string and arguments.
|
||||
*
|
||||
* @return an index into the array of options indicating the user's choice,
|
||||
* or -1 if the user cancelled the choice.
|
||||
*/
|
||||
int showChoice(String[] options, String... message);
|
||||
|
||||
/**
|
||||
* Asks the user to confirm an action and returns the user's response. The
|
||||
* message may consist of a translatable format string and arguments.
|
||||
*/
|
||||
boolean showConfirmationMessage(String... message);
|
||||
|
||||
/**
|
||||
* Shows a message to the user. The message may consist of a translatable
|
||||
* format string and arguments.
|
||||
*/
|
||||
void showMessage(String... message);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
public class IoUtils {
|
||||
@@ -25,18 +25,21 @@ public class IoUtils {
|
||||
delete(f);
|
||||
} else if (f.isDirectory()) {
|
||||
File[] children = f.listFiles();
|
||||
if (children != null)
|
||||
if (children == null) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Could not list files in "
|
||||
+ f.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
for (File child : children) deleteFileOrDir(child);
|
||||
}
|
||||
delete(f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void delete(File f) {
|
||||
boolean deleted = f.delete();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (deleted) LOG.info("Deleted " + f.getAbsolutePath());
|
||||
else LOG.info("Could not delete " + f.getAbsolutePath());
|
||||
}
|
||||
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||
}
|
||||
|
||||
public static void copyAndClose(InputStream in, OutputStream out) {
|
||||
|
||||
@@ -22,19 +22,6 @@ public class OsUtils {
|
||||
return os != null && os.contains("Mac OS");
|
||||
}
|
||||
|
||||
public static boolean isMacLeopardOrNewer() {
|
||||
if (!isMac() || version == null) return false;
|
||||
try {
|
||||
String[] v = version.split("\\.");
|
||||
if (v.length != 3) return false;
|
||||
int major = Integer.parseInt(v[0]);
|
||||
int minor = Integer.parseInt(v[1]);
|
||||
return major >= 10 && minor >= 5;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return os != null && os.contains("Linux") && !isAndroid();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionManager;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
@@ -29,17 +30,19 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.ui.UiCallback;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -57,12 +60,15 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
Logger.getLogger(PluginManagerImpl.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final EventBus eventBus;
|
||||
private final PluginConfig pluginConfig;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
private final SettingsManager settingsManager;
|
||||
private final TransportPropertyManager transportPropertyManager;
|
||||
private final UiCallback uiCallback;
|
||||
private final SecureRandom random;
|
||||
private final Clock clock;
|
||||
private final Map<TransportId, Plugin> plugins;
|
||||
private final List<SimplexPlugin> simplexPlugins;
|
||||
private final List<DuplexPlugin> duplexPlugins;
|
||||
@@ -70,27 +76,41 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
@Inject
|
||||
PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
|
||||
PluginManagerImpl(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler, EventBus eventBus,
|
||||
PluginConfig pluginConfig, ConnectionManager connectionManager,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
SettingsManager settingsManager,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
UiCallback uiCallback) {
|
||||
SecureRandom random, Clock clock) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.connectionManager = connectionManager;
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
this.settingsManager = settingsManager;
|
||||
this.transportPropertyManager = transportPropertyManager;
|
||||
this.uiCallback = uiCallback;
|
||||
this.random = random;
|
||||
this.clock = clock;
|
||||
plugins = new ConcurrentHashMap<>();
|
||||
simplexPlugins = new CopyOnWriteArrayList<>();
|
||||
duplexPlugins = new CopyOnWriteArrayList<>();
|
||||
startLatches = new ConcurrentHashMap<>();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
public void startService() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
// Instantiate the poller
|
||||
if (pluginConfig.shouldPoll()) {
|
||||
LOG.info("Starting poller");
|
||||
Poller poller = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, this, transportPropertyManager, random,
|
||||
clock);
|
||||
eventBus.addListener(poller);
|
||||
}
|
||||
// Instantiate the simplex plugins and start them asynchronously
|
||||
LOG.info("Starting simplex plugins");
|
||||
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
|
||||
@@ -273,26 +293,6 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties() {
|
||||
try {
|
||||
return transportPropertyManager.getRemoteProperties(id);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c) {
|
||||
try {
|
||||
return transportPropertyManager.getRemoteProperties(c, id);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return new TransportProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeSettings(Settings s) {
|
||||
try {
|
||||
@@ -311,21 +311,6 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return uiCallback.showChoice(options, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return uiCallback.showConfirmationMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String... message) {
|
||||
uiCallback.showMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transportEnabled() {
|
||||
eventBus.broadcast(new TransportEnabledEvent(id));
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionManager;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -26,8 +18,6 @@ public class PluginModule {
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
PluginManager pluginManager;
|
||||
@Inject
|
||||
Poller poller;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -35,19 +25,6 @@ public class PluginModule {
|
||||
return new BackoffFactoryImpl();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Poller providePoller(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
ConnectionManager connectionManager,
|
||||
ConnectionRegistry connectionRegistry, PluginManager pluginManager,
|
||||
SecureRandom random, Clock clock, EventBus eventBus) {
|
||||
Poller poller = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
eventBus.addListener(poller);
|
||||
return poller;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ConnectionManager provideConnectionManager(
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
@@ -19,12 +20,17 @@ import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -33,10 +39,10 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
@@ -49,26 +55,30 @@ class Poller implements EventListener {
|
||||
private final ConnectionManager connectionManager;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
private final PluginManager pluginManager;
|
||||
private final TransportPropertyManager transportPropertyManager;
|
||||
private final SecureRandom random;
|
||||
private final Clock clock;
|
||||
private final Lock lock;
|
||||
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
||||
private final Set<TransportId> polling; // Locking: lock
|
||||
|
||||
@Inject
|
||||
Poller(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
ConnectionManager connectionManager,
|
||||
ConnectionRegistry connectionRegistry, PluginManager pluginManager,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
SecureRandom random, Clock clock) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.connectionManager = connectionManager;
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
this.pluginManager = pluginManager;
|
||||
this.transportPropertyManager = transportPropertyManager;
|
||||
this.random = random;
|
||||
this.clock = clock;
|
||||
lock = new ReentrantLock();
|
||||
tasks = new HashMap<>();
|
||||
polling = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,10 +130,15 @@ class Poller implements EventListener {
|
||||
private void connectToContact(ContactId c, SimplexPlugin p) {
|
||||
ioExecutor.execute(() -> {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
TransportConnectionWriter w = p.createWriter(c);
|
||||
if (connectionRegistry.isConnected(c, t)) return;
|
||||
try {
|
||||
TransportProperties props =
|
||||
transportPropertyManager.getRemoteProperties(c, t);
|
||||
TransportConnectionWriter w = p.createWriter(props);
|
||||
if (w != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, w);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -131,10 +146,15 @@ class Poller implements EventListener {
|
||||
private void connectToContact(ContactId c, DuplexPlugin p) {
|
||||
ioExecutor.execute(() -> {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
DuplexTransportConnection d = p.createConnection(c);
|
||||
if (connectionRegistry.isConnected(c, t)) return;
|
||||
try {
|
||||
TransportProperties props =
|
||||
transportPropertyManager.getRemoteProperties(c, t);
|
||||
DuplexTransportConnection d = p.createConnection(props);
|
||||
if (d != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, d);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -186,7 +206,17 @@ class Poller implements EventListener {
|
||||
private void poll(Plugin p) {
|
||||
TransportId t = p.getId();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
|
||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||
try {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
transportPropertyManager.getRemoteProperties(t);
|
||||
Collection<ContactId> connected =
|
||||
connectionRegistry.getConnectedContacts(t);
|
||||
remote = new HashMap<>(remote);
|
||||
remote.keySet().removeAll(connected);
|
||||
if (!remote.isEmpty()) p.poll(remote);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class ScheduledPollTask {
|
||||
@@ -215,20 +245,33 @@ class Poller implements EventListener {
|
||||
@Override
|
||||
@IoExecutor
|
||||
public void run() {
|
||||
TransportId t = plugin.getId();
|
||||
boolean shouldPoll;
|
||||
lock.lock();
|
||||
try {
|
||||
TransportId t = plugin.getId();
|
||||
ScheduledPollTask scheduled = tasks.get(t);
|
||||
if (scheduled != null && scheduled.task != this)
|
||||
return; // Replaced by another task
|
||||
tasks.remove(t);
|
||||
// Don't poll again if last poll is still running
|
||||
shouldPoll = polling.add(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
int delay = plugin.getPollingInterval();
|
||||
if (randomiseNext) delay = (int) (delay * random.nextDouble());
|
||||
schedule(plugin, delay, false);
|
||||
poll(plugin);
|
||||
if (shouldPoll) {
|
||||
poll(plugin);
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Last poll for " + t + " is still running");
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
polling.remove(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -53,6 +54,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
/**
|
||||
* How many milliseconds to pause between connection attempts when
|
||||
* polling, to avoid interfering with other Bluetooth or wifi connections.
|
||||
*/
|
||||
private static final int POLLING_PAUSE_MS = 1000;
|
||||
|
||||
final BluetoothConnectionLimiter connectionLimiter;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
@@ -250,30 +257,42 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
public void poll(Map<ContactId, TransportProperties> contacts) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
// Try to connect to known devices in a random order
|
||||
List<ContactId> keys = new ArrayList<>(contacts.keySet());
|
||||
Collections.shuffle(keys);
|
||||
ioExecutor.execute(() -> {
|
||||
boolean first = true;
|
||||
for (ContactId c : keys) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return;
|
||||
TransportProperties p = contacts.get(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
// Pause between connection attempts
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(POLLING_PAUSE_MS);
|
||||
} catch (InterruptedException ex) {
|
||||
LOG.info("Interrupted while polling");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) {
|
||||
backoff.reset();
|
||||
if (connectionLimiter.contactConnectionOpened(conn))
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -308,10 +327,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MIN_STREAM_LENGTH;
|
||||
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@NotNullByDefault
|
||||
abstract class FilePlugin implements SimplexPlugin {
|
||||
@@ -29,25 +23,15 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FilePlugin.class.getName());
|
||||
|
||||
protected final Executor ioExecutor;
|
||||
protected final SimplexPluginCallback callback;
|
||||
protected final int maxLatency;
|
||||
protected final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
protected volatile boolean running = false;
|
||||
protected abstract void writerFinished(File f, boolean exception);
|
||||
|
||||
@Nullable
|
||||
protected abstract File chooseOutputDirectory();
|
||||
protected abstract void readerFinished(File f, boolean exception,
|
||||
boolean recognised);
|
||||
|
||||
protected abstract Collection<File> findFilesByName(String filename);
|
||||
|
||||
protected abstract void writerFinished(File f);
|
||||
|
||||
protected abstract void readerFinished(File f);
|
||||
|
||||
protected FilePlugin(Executor ioExecutor, SimplexPluginCallback callback,
|
||||
int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
FilePlugin(SimplexPluginCallback callback, int maxLatency) {
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
@@ -58,81 +42,36 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return Integer.MAX_VALUE; // We don't need keepalives
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionReader createReader(ContactId c) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionWriter createWriter(ContactId c) {
|
||||
if (!running) return null;
|
||||
return createWriter(createConnectionFilename());
|
||||
}
|
||||
|
||||
private String createConnectionFilename() {
|
||||
StringBuilder s = new StringBuilder(12);
|
||||
for (int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26));
|
||||
s.append(".dat");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
boolean isPossibleConnectionFilename(String filename) {
|
||||
return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TransportConnectionWriter createWriter(String filename) {
|
||||
if (!running) return null;
|
||||
File dir = chooseOutputDirectory();
|
||||
if (dir == null || !dir.exists() || !dir.isDirectory()) return null;
|
||||
File f = new File(dir, filename);
|
||||
public TransportConnectionReader createReader(TransportProperties p) {
|
||||
if (!isRunning()) return null;
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) return null;
|
||||
try {
|
||||
long capacity = dir.getFreeSpace();
|
||||
if (capacity < MIN_STREAM_LENGTH) return null;
|
||||
OutputStream out = new FileOutputStream(f);
|
||||
return new FileTransportWriter(f, out, capacity, this);
|
||||
File file = new File(path);
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
return new FileTransportReader(file, in, this);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
f.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void createReaderFromFile(File f) {
|
||||
if (!running) return;
|
||||
ioExecutor.execute(new ReaderCreator(f));
|
||||
}
|
||||
|
||||
private class ReaderCreator implements Runnable {
|
||||
|
||||
private final File file;
|
||||
|
||||
private ReaderCreator(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isPossibleConnectionFilename(file.getName())) {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
callback.readerCreated(new FileTransportReader(file, in,
|
||||
FilePlugin.this));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
@Override
|
||||
public TransportConnectionWriter createWriter(TransportProperties p) {
|
||||
if (!isRunning()) return null;
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) return null;
|
||||
try {
|
||||
File file = new File(path);
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
LOG.info("Failed to create file");
|
||||
return null;
|
||||
}
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
return new FileTransportWriter(file, out, this);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,6 @@ class FileTransportReader implements TransportConnectionReader {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
if (recognised) {
|
||||
file.delete();
|
||||
plugin.readerFinished(file);
|
||||
}
|
||||
plugin.readerFinished(file, exception, recognised);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,11 @@ class FileTransportWriter implements TransportConnectionWriter {
|
||||
|
||||
private final File file;
|
||||
private final OutputStream out;
|
||||
private final long capacity;
|
||||
private final FilePlugin plugin;
|
||||
|
||||
FileTransportWriter(File file, OutputStream out, long capacity,
|
||||
FilePlugin plugin) {
|
||||
FileTransportWriter(File file, OutputStream out, FilePlugin plugin) {
|
||||
this.file = file;
|
||||
this.out = out;
|
||||
this.capacity = capacity;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@@ -39,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter {
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
@@ -56,7 +48,6 @@ class FileTransportWriter implements TransportConnectionWriter {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
if (exception) file.delete();
|
||||
else plugin.writerFinished(file);
|
||||
plugin.writerFinished(file, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,20 +207,16 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
public void poll(Map<ContactId, TransportProperties> contacts) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
|
||||
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
||||
connectAndCallBack(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(ContactId c, TransportProperties p) {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
@@ -230,13 +226,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (!isRunning()) return null;
|
||||
return createConnection(callback.getRemoteProperties(c));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
|
||||
if (!isConnectable(remote)) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionManager;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -13,16 +14,18 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.ui.UiCallback;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.concurrent.Synchroniser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
@@ -34,15 +37,20 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor ioExecutor = Executors.newSingleThreadExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
SecureRandom random = new SecureRandom();
|
||||
Clock clock = context.mock(Clock.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
SettingsManager settingsManager =
|
||||
context.mock(SettingsManager.class);
|
||||
TransportPropertyManager transportPropertyManager =
|
||||
context.mock(TransportPropertyManager.class);
|
||||
UiCallback uiCallback = context.mock(UiCallback.class);
|
||||
|
||||
// Two simplex plugin factories: both create plugins, one fails to start
|
||||
SimplexPluginFactory simplexFactory =
|
||||
@@ -71,6 +79,8 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
||||
will(returnValue(simplexFailId));
|
||||
allowing(duplexPlugin).getId();
|
||||
will(returnValue(duplexId));
|
||||
allowing(pluginConfig).shouldPoll();
|
||||
will(returnValue(false));
|
||||
// start()
|
||||
// First simplex plugin
|
||||
oneOf(pluginConfig).getSimplexFactories();
|
||||
@@ -112,9 +122,9 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
||||
oneOf(duplexPlugin).stop();
|
||||
}});
|
||||
|
||||
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
|
||||
pluginConfig, connectionManager, settingsManager,
|
||||
transportPropertyManager, uiCallback);
|
||||
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler,
|
||||
eventBus, pluginConfig, connectionManager, connectionRegistry,
|
||||
settingsManager, transportPropertyManager, random, clock);
|
||||
|
||||
// Two plugins should be started and stopped
|
||||
p.startService();
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
@@ -24,13 +26,15 @@ import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
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.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
@@ -44,6 +48,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
context.mock(ConnectionRegistry.class);
|
||||
private final PluginManager pluginManager =
|
||||
context.mock(PluginManager.class);
|
||||
private final TransportPropertyManager transportPropertyManager =
|
||||
context.mock(TransportPropertyManager.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final ScheduledFuture future = context.mock(ScheduledFuture.class);
|
||||
private final SecureRandom random;
|
||||
@@ -51,6 +57,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
private final Executor ioExecutor = new ImmediateExecutor();
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final TransportProperties properties = new TransportProperties();
|
||||
private final int pollingInterval = 60 * 1000;
|
||||
private final long now = System.currentTimeMillis();
|
||||
|
||||
@@ -66,8 +73,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
SimplexPlugin simplexPlugin1 =
|
||||
context.mock(SimplexPlugin.class, "simplexPlugin1");
|
||||
TransportId simplexId1 = getTransportId();
|
||||
List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
|
||||
simplexPlugin1);
|
||||
List<SimplexPlugin> simplexPlugins =
|
||||
asList(simplexPlugin, simplexPlugin1);
|
||||
TransportConnectionWriter simplexWriter =
|
||||
context.mock(TransportConnectionWriter.class);
|
||||
|
||||
@@ -76,8 +83,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
TransportId duplexId = getTransportId();
|
||||
DuplexPlugin duplexPlugin1 =
|
||||
context.mock(DuplexPlugin.class, "duplexPlugin1");
|
||||
List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
|
||||
duplexPlugin1);
|
||||
List<DuplexPlugin> duplexPlugins =
|
||||
asList(duplexPlugin, duplexPlugin1);
|
||||
DuplexTransportConnection duplexConnection =
|
||||
context.mock(DuplexTransportConnection.class);
|
||||
|
||||
@@ -96,8 +103,12 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(simplexId1));
|
||||
oneOf(connectionRegistry).isConnected(contactId, simplexId1);
|
||||
will(returnValue(false));
|
||||
// Get the transport properties
|
||||
oneOf(transportPropertyManager).getRemoteProperties(contactId,
|
||||
simplexId1);
|
||||
will(returnValue(properties));
|
||||
// Connect to the contact
|
||||
oneOf(simplexPlugin1).createWriter(contactId);
|
||||
oneOf(simplexPlugin1).createWriter(properties);
|
||||
will(returnValue(simplexWriter));
|
||||
// Pass the connection to the connection manager
|
||||
oneOf(connectionManager).manageOutgoingConnection(contactId,
|
||||
@@ -105,7 +116,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
// Get the duplex plugins
|
||||
oneOf(pluginManager).getDuplexPlugins();
|
||||
will(returnValue(duplexPlugins));
|
||||
// The first plugin supports polling
|
||||
// The duplex plugin supports polling
|
||||
oneOf(duplexPlugin).shouldPoll();
|
||||
will(returnValue(true));
|
||||
// Check whether the contact is already connected
|
||||
@@ -113,8 +124,12 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(duplexId));
|
||||
oneOf(connectionRegistry).isConnected(contactId, duplexId);
|
||||
will(returnValue(false));
|
||||
// Get the transport properties
|
||||
oneOf(transportPropertyManager).getRemoteProperties(contactId,
|
||||
duplexId);
|
||||
will(returnValue(properties));
|
||||
// Connect to the contact
|
||||
oneOf(duplexPlugin).createConnection(contactId);
|
||||
oneOf(duplexPlugin).createConnection(properties);
|
||||
will(returnValue(duplexConnection));
|
||||
// Pass the connection to the connection manager
|
||||
oneOf(connectionManager).manageOutgoingConnection(contactId,
|
||||
@@ -125,7 +140,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
||||
}
|
||||
@@ -165,8 +181,12 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
// Check whether the contact is already connected
|
||||
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
||||
will(returnValue(false));
|
||||
// Get the transport properties
|
||||
oneOf(transportPropertyManager).getRemoteProperties(contactId,
|
||||
transportId);
|
||||
will(returnValue(properties));
|
||||
// Connect to the contact
|
||||
oneOf(plugin).createConnection(contactId);
|
||||
oneOf(plugin).createConnection(properties);
|
||||
will(returnValue(duplexConnection));
|
||||
// Pass the connection to the connection manager
|
||||
oneOf(connectionManager).manageOutgoingConnection(contactId,
|
||||
@@ -174,15 +194,15 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
||||
false));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRescheduleOnConnectionOpened() throws Exception {
|
||||
public void testRescheduleOnConnectionOpened() {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -205,14 +225,15 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
||||
public void testRescheduleDoesNotReplaceEarlierTask() {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -248,7 +269,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
@@ -257,7 +279,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduleReplacesLaterTask() throws Exception {
|
||||
public void testRescheduleReplacesLaterTask() {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -296,7 +318,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
@@ -306,8 +329,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testPollsOnTransportEnabled() throws Exception {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
List<ContactId> connected = Collections.singletonList(contactId);
|
||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
@@ -335,20 +357,69 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// Poll the plugin
|
||||
// Get the transport properties and connected contacts
|
||||
oneOf(transportPropertyManager).getRemoteProperties(transportId);
|
||||
will(returnValue(singletonMap(contactId, properties)));
|
||||
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
||||
will(returnValue(connected));
|
||||
oneOf(plugin).poll(connected);
|
||||
will(returnValue(emptyList()));
|
||||
// Poll the plugin
|
||||
oneOf(plugin).poll(singletonMap(contactId, properties));
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
||||
public void testDoesNotPollIfAllContactsAreConnected() throws Exception {
|
||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
will(returnValue(transportId));
|
||||
// Get the plugin
|
||||
oneOf(pluginManager).getPlugin(transportId);
|
||||
will(returnValue(plugin));
|
||||
// The plugin supports polling
|
||||
oneOf(plugin).shouldPoll();
|
||||
will(returnValue(true));
|
||||
// Schedule a polling task immediately
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
will(new RunAction());
|
||||
// Running the polling task schedules the next polling task
|
||||
oneOf(plugin).getPollingInterval();
|
||||
will(returnValue(pollingInterval));
|
||||
oneOf(random).nextDouble();
|
||||
will(returnValue(0.5));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// Get the transport properties and connected contacts
|
||||
oneOf(transportPropertyManager).getRemoteProperties(transportId);
|
||||
will(returnValue(singletonMap(contactId, properties)));
|
||||
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
||||
will(returnValue(singletonList(contactId)));
|
||||
// All contacts are connected, so don't poll the plugin
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsPollingOnTransportDisabled() {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -371,7 +442,8 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
connectionRegistry, pluginManager, transportPropertyManager,
|
||||
random, clock);
|
||||
|
||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||
p.eventOccurred(new TransportDisabledEvent(transportId));
|
||||
|
||||
@@ -23,8 +23,6 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -40,7 +38,6 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LanTcpPluginTest extends BrambleTestCase {
|
||||
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final Backoff backoff = new TestBackoff();
|
||||
|
||||
@Test
|
||||
@@ -160,12 +157,10 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
error.set(true);
|
||||
}
|
||||
}).start();
|
||||
// Tell the plugin about the port
|
||||
// Connect to the port
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("ipPorts", addrString + ":" + port);
|
||||
callback.remote.put(contactId, p);
|
||||
// Connect to the port
|
||||
DuplexTransportConnection d = plugin.createConnection(contactId);
|
||||
DuplexTransportConnection d = plugin.createConnection(p);
|
||||
assertNotNull(d);
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
@@ -281,7 +276,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersNonZeroPorts() throws Exception {
|
||||
public void testComparatorPrefersNonZeroPorts() {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
|
||||
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
|
||||
@@ -294,7 +289,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersLongerPrefixes() throws Exception {
|
||||
public void testComparatorPrefersLongerPrefixes() {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||
@@ -314,7 +309,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception {
|
||||
public void testComparatorPrefersSiteLocalToLinkLocal() {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||
@@ -345,8 +340,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
@NotNullByDefault
|
||||
private static class Callback implements DuplexPluginCallback {
|
||||
|
||||
private final Map<ContactId, TransportProperties> remote =
|
||||
new Hashtable<>();
|
||||
private final CountDownLatch propertiesLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch connectionsLatch = new CountDownLatch(1);
|
||||
private final TransportProperties local = new TransportProperties();
|
||||
@@ -361,16 +354,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
return local;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c) {
|
||||
return remote.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeSettings(Settings s) {
|
||||
}
|
||||
@@ -381,20 +364,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
propertiesLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String... message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingConnectionCreated(DuplexTransportConnection d) {
|
||||
connectionsLatch.countDown();
|
||||
|
||||
@@ -20,7 +20,7 @@ public class CaptureArgumentAction<T> implements Action {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
public Object invoke(Invocation invocation) {
|
||||
captured.set(capturedClass.cast(invocation.getParameter(index)));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,25 +9,31 @@ import java.io.File;
|
||||
@NotNullByDefault
|
||||
public class TestDatabaseConfig implements DatabaseConfig {
|
||||
|
||||
private final File dir;
|
||||
private final File dbDir, keyDir;
|
||||
private final long maxSize;
|
||||
private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]);
|
||||
|
||||
public TestDatabaseConfig(File dir, long maxSize) {
|
||||
this.dir = dir;
|
||||
public TestDatabaseConfig(File testDir, long maxSize) {
|
||||
dbDir = new File(testDir, "db");
|
||||
keyDir = new File(testDir, "key");
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean databaseExists() {
|
||||
if (!dir.isDirectory()) return false;
|
||||
File[] files = dir.listFiles();
|
||||
if (!dbDir.isDirectory()) return false;
|
||||
File[] files = dbDir.listFiles();
|
||||
return files != null && files.length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabaseDirectory() {
|
||||
return dir;
|
||||
return dbDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabaseKeyDirectory() {
|
||||
return keyDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,13 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
@Module
|
||||
@@ -51,12 +52,17 @@ public class TestPluginConfigModule {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return Collections.emptyList();
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return Collections.singletonList(simplex);
|
||||
return singletonList(simplex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
return pluginConfig;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,20 +10,20 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@Module
|
||||
public class DesktopPluginModule extends PluginModule {
|
||||
|
||||
@@ -41,12 +41,8 @@ public class DesktopPluginModule extends PluginModule {
|
||||
backoffFactory);
|
||||
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
|
||||
backoffFactory, shutdownManager);
|
||||
SimplexPluginFactory removable =
|
||||
new RemovableDrivePluginFactory(ioExecutor);
|
||||
Collection<SimplexPluginFactory> simplex =
|
||||
Collections.singletonList(removable);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, modem, lan, wan);
|
||||
asList(bluetooth, modem, lan, wan);
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -57,7 +53,12 @@ public class DesktopPluginModule extends PluginModule {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return simplex;
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return pluginConfig;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
class LinuxRemovableDriveFinder extends UnixRemovableDriveFinder {
|
||||
|
||||
@Override
|
||||
protected String getMountCommand() {
|
||||
return "/bin/mount";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected String parseMountPoint(String line) {
|
||||
// The format is "/dev/foo on /bar/baz type bam (opt1,opt2)"
|
||||
String pattern = "^/dev/[^ ]+ on (.*) type [^ ]+ \\([^)]+\\)$";
|
||||
String path = line.replaceFirst(pattern, "$1");
|
||||
return path.equals(line) ? null : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRemovableDriveMountPoint(String path) {
|
||||
return path.startsWith("/mnt/") || path.startsWith("/media/");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
class LinuxRemovableDriveMonitor extends UnixRemovableDriveMonitor {
|
||||
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] {"/mnt", "/media"};
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
class MacRemovableDriveFinder extends UnixRemovableDriveFinder {
|
||||
|
||||
@Override
|
||||
protected String getMountCommand() {
|
||||
return "/sbin/mount";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected String parseMountPoint(String line) {
|
||||
// The format is "/dev/foo on /bar/baz (opt1, opt2)"
|
||||
String pattern = "^/dev/[^ ]+ on (.*) \\([^)]+\\)$";
|
||||
String path = line.replaceFirst(pattern, "$1");
|
||||
return path.equals(line) ? null : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRemovableDriveMountPoint(String path) {
|
||||
return path.startsWith("/Volumes/");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
class MacRemovableDriveMonitor extends UnixRemovableDriveMonitor {
|
||||
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] {"/Volumes"};
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final RemovableDriveFinder finder;
|
||||
private final int pollingInterval;
|
||||
|
||||
private final Lock pollingLock = new ReentrantLock();
|
||||
private final Condition stopPolling = pollingLock.newCondition();
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile Callback callback = null;
|
||||
|
||||
PollingRemovableDriveMonitor(Executor ioExecutor,
|
||||
RemovableDriveFinder finder, int pollingInterval) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.finder = finder;
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Callback callback) throws IOException {
|
||||
this.callback = callback;
|
||||
running = true;
|
||||
ioExecutor.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws IOException {
|
||||
running = false;
|
||||
pollingLock.lock();
|
||||
try {
|
||||
stopPolling.signalAll();
|
||||
} finally {
|
||||
pollingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Collection<File> drives = finder.findRemovableDrives();
|
||||
while (running) {
|
||||
pollingLock.lock();
|
||||
try {
|
||||
stopPolling.await(pollingInterval, MILLISECONDS);
|
||||
} finally {
|
||||
pollingLock.unlock();
|
||||
}
|
||||
if (!running) return;
|
||||
Collection<File> newDrives = finder.findRemovableDrives();
|
||||
for (File f : newDrives) {
|
||||
if (!drives.contains(f)) callback.driveInserted(f);
|
||||
}
|
||||
drives = newDrives;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting to poll");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException e) {
|
||||
callback.exceptionThrown(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveFinder {
|
||||
|
||||
Collection<File> findRemovableDrives() throws IOException;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveMonitor {
|
||||
|
||||
void start(Callback c) throws IOException;
|
||||
|
||||
void stop() throws IOException;
|
||||
|
||||
interface Callback {
|
||||
|
||||
void driveInserted(File root);
|
||||
|
||||
void exceptionThrown(IOException e);
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDrivePlugin extends FilePlugin
|
||||
implements RemovableDriveMonitor.Callback {
|
||||
|
||||
static final TransportId ID =
|
||||
new TransportId("org.briarproject.bramble.file");
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(RemovableDrivePlugin.class.getName());
|
||||
|
||||
private final RemovableDriveFinder finder;
|
||||
private final RemovableDriveMonitor monitor;
|
||||
|
||||
RemovableDrivePlugin(Executor ioExecutor, SimplexPluginCallback callback,
|
||||
RemovableDriveFinder finder, RemovableDriveMonitor monitor,
|
||||
int maxLatency) {
|
||||
super(ioExecutor, callback, maxLatency);
|
||||
this.finder = finder;
|
||||
this.monitor = monitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
running = true;
|
||||
try {
|
||||
monitor.start(this);
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws PluginException {
|
||||
running = false;
|
||||
try {
|
||||
monitor.stop();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File chooseOutputDirectory() {
|
||||
try {
|
||||
List<File> drives = new ArrayList<>(finder.findRemovableDrives());
|
||||
if (drives.isEmpty()) return null;
|
||||
String[] paths = new String[drives.size()];
|
||||
for (int i = 0; i < paths.length; i++) {
|
||||
paths[i] = drives.get(i).getPath();
|
||||
}
|
||||
int i = callback.showChoice(paths, "REMOVABLE_DRIVE_CHOOSE_DRIVE");
|
||||
if (i == -1) return null;
|
||||
return drives.get(i);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readerFinished(File f) {
|
||||
callback.showMessage("REMOVABLE_DRIVE_READ_FINISHED");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writerFinished(File f) {
|
||||
callback.showMessage("REMOVABLE_DRIVE_WRITE_FINISHED");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<File> findFilesByName(String filename) {
|
||||
List<File> matches = new ArrayList<>();
|
||||
try {
|
||||
for (File drive : finder.findRemovableDrives()) {
|
||||
File[] files = drive.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isFile() && filename.equals(f.getName()))
|
||||
matches.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void driveInserted(File root) {
|
||||
File[] files = root.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) if (f.isFile()) createReaderFromFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionThrown(IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||
|
||||
// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
|
||||
private static final int MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
|
||||
private static final int POLLING_INTERVAL = 10 * 1000; // 10 seconds
|
||||
|
||||
private final Executor ioExecutor;
|
||||
|
||||
public RemovableDrivePluginFactory(Executor ioExecutor) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return RemovableDrivePlugin.ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
|
||||
RemovableDriveFinder finder;
|
||||
RemovableDriveMonitor monitor;
|
||||
if (OsUtils.isLinux()) {
|
||||
finder = new LinuxRemovableDriveFinder();
|
||||
monitor = new LinuxRemovableDriveMonitor();
|
||||
} else if (OsUtils.isMacLeopardOrNewer()) {
|
||||
finder = new MacRemovableDriveFinder();
|
||||
monitor = new MacRemovableDriveMonitor();
|
||||
} else if (OsUtils.isMac()) {
|
||||
// JNotify requires OS X 10.5 or newer, so we have to poll
|
||||
finder = new MacRemovableDriveFinder();
|
||||
monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
|
||||
POLLING_INTERVAL);
|
||||
} else if (OsUtils.isWindows()) {
|
||||
finder = new WindowsRemovableDriveFinder();
|
||||
monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
|
||||
POLLING_INTERVAL);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return new RemovableDrivePlugin(ioExecutor, callback, finder, monitor,
|
||||
MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
abstract class UnixRemovableDriveFinder implements RemovableDriveFinder {
|
||||
|
||||
protected abstract String getMountCommand();
|
||||
|
||||
@Nullable
|
||||
protected abstract String parseMountPoint(String line);
|
||||
|
||||
protected abstract boolean isRemovableDriveMountPoint(String path);
|
||||
|
||||
@Override
|
||||
public List<File> findRemovableDrives() throws IOException {
|
||||
List<File> drives = new ArrayList<>();
|
||||
Process p = new ProcessBuilder(getMountCommand()).start();
|
||||
Scanner s = new Scanner(p.getInputStream(), "UTF-8");
|
||||
try {
|
||||
while (s.hasNextLine()) {
|
||||
String line = s.nextLine();
|
||||
String[] tokens = line.split(" ");
|
||||
if (tokens.length < 3) continue;
|
||||
// The general format is "/dev/foo on /bar/baz ..."
|
||||
if (tokens[0].startsWith("/dev/") && tokens[1].equals("on")) {
|
||||
// The path may contain spaces so we can't use tokens[2]
|
||||
String path = parseMountPoint(line);
|
||||
if (path != null && isRemovableDriveMountPoint(path)) {
|
||||
File f = new File(path);
|
||||
if (f.exists() && f.isDirectory()) drives.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
s.close();
|
||||
}
|
||||
return drives;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import net.contentobjects.jnotify.JNotify;
|
||||
import net.contentobjects.jnotify.JNotifyListener;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class UnixRemovableDriveMonitor implements RemovableDriveMonitor,
|
||||
JNotifyListener {
|
||||
|
||||
//TODO: rationalise this in a further refactor
|
||||
private static final Lock staticLock = new ReentrantLock();
|
||||
|
||||
// The following are locking: staticLock
|
||||
private static boolean triedLoad = false;
|
||||
private static Throwable loadError = null;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private final List<Integer> watches = new ArrayList<>();
|
||||
private boolean started = false;
|
||||
private Callback callback = null;
|
||||
|
||||
protected abstract String[] getPathsToWatch();
|
||||
|
||||
@Nullable
|
||||
private static Throwable tryLoad() {
|
||||
try {
|
||||
Class.forName("net.contentobjects.jnotify.JNotify");
|
||||
return null;
|
||||
} catch (UnsatisfiedLinkError | ClassNotFoundException e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkEnabled() throws IOException {
|
||||
staticLock.lock();
|
||||
try {
|
||||
if (!triedLoad) {
|
||||
loadError = tryLoad();
|
||||
triedLoad = true;
|
||||
}
|
||||
if (loadError != null) throw new IOException(loadError.toString());
|
||||
} finally {
|
||||
staticLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Callback callback) throws IOException {
|
||||
checkEnabled();
|
||||
List<Integer> watches = new ArrayList<>();
|
||||
int mask = JNotify.FILE_CREATED;
|
||||
for (String path : getPathsToWatch()) {
|
||||
if (new File(path).exists())
|
||||
watches.add(JNotify.addWatch(path, mask, false, this));
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
if (started) throw new AssertionError();
|
||||
if (this.callback != null) throw new AssertionError();
|
||||
started = true;
|
||||
this.callback = callback;
|
||||
this.watches.addAll(watches);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws IOException {
|
||||
checkEnabled();
|
||||
List<Integer> watches;
|
||||
lock.lock();
|
||||
try {
|
||||
if (!started) throw new AssertionError();
|
||||
if (callback == null) throw new AssertionError();
|
||||
started = false;
|
||||
callback = null;
|
||||
watches = new ArrayList<>(this.watches);
|
||||
this.watches.clear();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (Integer w : watches) JNotify.removeWatch(w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileCreated(int wd, String rootPath, String name) {
|
||||
Callback callback;
|
||||
lock.lock();
|
||||
try {
|
||||
callback = this.callback;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
if (callback != null)
|
||||
callback.driveInserted(new File(rootPath + "/" + name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileDeleted(int wd, String rootPath, String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileModified(int wd, String rootPath, String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileRenamed(int wd, String rootPath, String oldName,
|
||||
String newName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@NotNullByDefault
|
||||
class WindowsRemovableDriveFinder implements RemovableDriveFinder {
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364939.aspx
|
||||
private static final int DRIVE_REMOVABLE = 2;
|
||||
|
||||
@Override
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
File[] roots = File.listRoots();
|
||||
if (roots == null) throw new IOException();
|
||||
List<File> drives = new ArrayList<>();
|
||||
for (File root : roots) {
|
||||
try {
|
||||
int type = Kernel32.INSTANCE.GetDriveType(root.getPath());
|
||||
if (type == DRIVE_REMOVABLE) drives.add(root);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
return drives;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -115,7 +115,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
public void poll(Map<ContactId, TransportProperties> contacts) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -139,17 +139,16 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (!running) return null;
|
||||
// Get the ISO 3166 code for the caller's country
|
||||
String fromIso = callback.getLocalProperties().get("iso3166");
|
||||
if (StringUtils.isNullOrEmpty(fromIso)) return null;
|
||||
// Get the ISO 3166 code for the callee's country
|
||||
TransportProperties properties = callback.getRemoteProperties(c);
|
||||
String toIso = properties.get("iso3166");
|
||||
String toIso = p.get("iso3166");
|
||||
if (StringUtils.isNullOrEmpty(toIso)) return null;
|
||||
// Get the callee's phone number
|
||||
String number = properties.get("number");
|
||||
String number = p.get("number");
|
||||
if (StringUtils.isNullOrEmpty(number)) return null;
|
||||
// Convert the number into direct dialling form
|
||||
number = CountryCodes.translate(number, fromIso, toIso);
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class LinuxRemovableDriveFinderTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testParseMountPoint() {
|
||||
LinuxRemovableDriveFinder f = new LinuxRemovableDriveFinder();
|
||||
String line = "/dev/sda3 on / type ext3"
|
||||
+ " (rw,errors=remount-ro,commit=0)";
|
||||
assertEquals("/", f.parseMountPoint(line));
|
||||
line = "gvfs-fuse-daemon on /home/alice/.gvfs"
|
||||
+ " type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=alice)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "fusectl on /sys/fs/fuse/connections type fusectl (rw)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "/dev/sdd1 on /media/HAZ SPACE(!) type vfat"
|
||||
+ " (rw,nosuid,nodev,uhelper=udisks,uid=1000,gid=1000,"
|
||||
+ "shortname=mixed,dmask=0077,utf8=1,showexec,flush)";
|
||||
assertEquals("/media/HAZ SPACE(!)", f.parseMountPoint(line));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MacRemovableDriveFinderTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testParseMountPoint() {
|
||||
MacRemovableDriveFinder f = new MacRemovableDriveFinder();
|
||||
String line = "/dev/disk0s3 on / (local, journaled)";
|
||||
assertEquals("/", f.parseMountPoint(line));
|
||||
line = "devfs on /dev (local)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "<volfs> on /.vol";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "automount -nsl [117] on /Network (automounted)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "/dev/disk1s1 on /Volumes/HAZ SPACE(!) (local, nodev, nosuid)";
|
||||
assertEquals("/Volumes/HAZ SPACE(!)", f.parseMountPoint(line));
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PollingRemovableDriveMonitorTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testOneCallbackPerFile() throws Exception {
|
||||
// Create a finder that returns no files the first time, then two files
|
||||
File file1 = new File("foo");
|
||||
File file2 = new File("bar");
|
||||
@NotNullByDefault
|
||||
RemovableDriveFinder finder = new RemovableDriveFinder() {
|
||||
|
||||
private AtomicBoolean firstCall = new AtomicBoolean(true);
|
||||
|
||||
@Override
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
if (firstCall.getAndSet(false)) return Collections.emptyList();
|
||||
else return Arrays.asList(file1, file2);
|
||||
}
|
||||
};
|
||||
// Create a callback that waits for two files
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<File> detected = new ArrayList<>();
|
||||
@NotNullByDefault
|
||||
Callback callback = new Callback() {
|
||||
|
||||
@Override
|
||||
public void driveInserted(File f) {
|
||||
detected.add(f);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
|
||||
Executors.newCachedThreadPool(), finder, 1);
|
||||
monitor.start(callback);
|
||||
// Wait for the monitor to detect the files
|
||||
assertTrue(latch.await(10, SECONDS));
|
||||
monitor.stop();
|
||||
// Check that both files were detected
|
||||
assertEquals(2, detected.size());
|
||||
assertTrue(detected.contains(file1));
|
||||
assertTrue(detected.contains(file2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionCallback() throws Exception {
|
||||
// Create a finder that throws an exception the second time it's polled
|
||||
RemovableDriveFinder finder = new RemovableDriveFinder() {
|
||||
|
||||
private AtomicBoolean firstCall = new AtomicBoolean(true);
|
||||
|
||||
@Override
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
if (firstCall.getAndSet(false)) return Collections.emptyList();
|
||||
else throw new IOException();
|
||||
}
|
||||
};
|
||||
// Create a callback that waits for an exception
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
@NotNullByDefault
|
||||
Callback callback = new Callback() {
|
||||
|
||||
@Override
|
||||
public void driveInserted(File root) {
|
||||
fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionThrown(IOException e) {
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
|
||||
Executors.newCachedThreadPool(), finder, 1);
|
||||
monitor.start(callback);
|
||||
assertTrue(latch.await(10, SECONDS));
|
||||
monitor.stop();
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.concurrent.Synchroniser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MIN_STREAM_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RemovableDrivePluginTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfNoDrivesAreFound() throws Exception {
|
||||
List<File> drives = Collections.emptyList();
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfNoDriveIsChosen() throws Exception {
|
||||
File drive1 = new File(testDir, "1");
|
||||
File drive2 = new File(testDir, "2");
|
||||
List<File> drives = new ArrayList<>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String[].class)));
|
||||
will(returnValue(-1)); // The user cancelled the choice
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfOutputDirDoesNotExist() throws Exception {
|
||||
File drive1 = new File(testDir, "1");
|
||||
File drive2 = new File(testDir, "2");
|
||||
List<File> drives = new ArrayList<>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String[].class)));
|
||||
will(returnValue(0)); // The user chose drive1 but it doesn't exist
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfOutputDirIsAFile() throws Exception {
|
||||
File drive1 = new File(testDir, "1");
|
||||
File drive2 = new File(testDir, "2");
|
||||
List<File> drives = new ArrayList<>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a file rather than a directory
|
||||
assertTrue(drive1.createNewFile());
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String[].class)));
|
||||
will(returnValue(0)); // The user chose drive1 but it's not a dir
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNotNullIfOutputDirIsADir() throws Exception {
|
||||
File drive1 = new File(testDir, "1");
|
||||
File drive2 = new File(testDir, "2");
|
||||
List<File> drives = new ArrayList<>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a directory
|
||||
assertTrue(drive1.mkdir());
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String[].class)));
|
||||
will(returnValue(0)); // The user chose drive1
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNotNull(plugin.createWriter(contactId));
|
||||
// The output file should exist and should be empty
|
||||
File[] files = drive1.listFiles();
|
||||
assertNotNull(files);
|
||||
assertEquals(1, files.length);
|
||||
assertEquals(0, files[0].length());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritingToWriter() throws Exception {
|
||||
File drive1 = new File(testDir, "1");
|
||||
File drive2 = new File(testDir, "2");
|
||||
List<File> drives = new ArrayList<>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a directory
|
||||
assertTrue(drive1.mkdir());
|
||||
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String[].class)));
|
||||
will(returnValue(0)); // The user chose drive1
|
||||
oneOf(callback).showMessage(with(any(String[].class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
TransportConnectionWriter writer = plugin.createWriter(contactId);
|
||||
assertNotNull(writer);
|
||||
// The output file should exist and should be empty
|
||||
File[] files = drive1.listFiles();
|
||||
assertNotNull(files);
|
||||
assertEquals(1, files.length);
|
||||
assertEquals(0, files[0].length());
|
||||
// Writing to the output stream should increase the size of the file
|
||||
OutputStream out = writer.getOutputStream();
|
||||
out.write(new byte[1234]);
|
||||
out.flush();
|
||||
out.close();
|
||||
// Disposing of the writer should not delete the file
|
||||
writer.dispose(false);
|
||||
assertTrue(files[0].exists());
|
||||
assertEquals(1234, files[0].length());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyDriveIsIgnored() throws Exception {
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
plugin.driveInserted(testDir);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilenames() {
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
Executor executor = context.mock(Executor.class);
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
callback, finder, monitor, 0);
|
||||
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefg.dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefghi.dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefgh_dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefgh.rat"));
|
||||
assertTrue(plugin.isPossibleConnectionFilename("abcdefgh.dat"));
|
||||
assertTrue(plugin.isPossibleConnectionFilename("ABCDEFGH.DAT"));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderIsCreated() throws Exception {
|
||||
Mockery context = new Mockery() {{
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(callback).readerCreated(with(any(FileTransportReader.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(
|
||||
new ImmediateExecutor(), callback, finder, monitor, 0);
|
||||
plugin.start();
|
||||
|
||||
File f = new File(testDir, "abcdefgh.dat");
|
||||
OutputStream out = new FileOutputStream(f);
|
||||
out.write(new byte[MIN_STREAM_LENGTH]);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertEquals(MIN_STREAM_LENGTH, f.length());
|
||||
plugin.driveInserted(testDir);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveMonitor.Callback;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class UnixRemovableDriveMonitorTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonexistentDir() throws Exception {
|
||||
if (!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
File doesNotExist = new File(testDir, "doesNotExist");
|
||||
RemovableDriveMonitor monitor = createMonitor(doesNotExist);
|
||||
@NotNullByDefault
|
||||
Callback callback = new Callback() {
|
||||
|
||||
@Override
|
||||
public void driveInserted(File root) {
|
||||
fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
monitor.start(callback);
|
||||
monitor.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneCallbackPerFile() throws Exception {
|
||||
if (!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Create a callback that will wait for two files before stopping
|
||||
List<File> detected = new ArrayList<>();
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
@NotNullByDefault
|
||||
Callback callback = new Callback() {
|
||||
|
||||
@Override
|
||||
public void driveInserted(File f) {
|
||||
detected.add(f);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
RemovableDriveMonitor monitor = createMonitor(testDir);
|
||||
monitor.start(callback);
|
||||
// Create two files in the test directory
|
||||
File file1 = new File(testDir, "1");
|
||||
File file2 = new File(testDir, "2");
|
||||
assertTrue(file1.createNewFile());
|
||||
assertTrue(file2.createNewFile());
|
||||
// Wait for the monitor to detect the files
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
monitor.stop();
|
||||
// Check that both files were detected
|
||||
assertEquals(2, detected.size());
|
||||
assertTrue(detected.contains(file1));
|
||||
assertTrue(detected.contains(file2));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private RemovableDriveMonitor createMonitor(File dir) {
|
||||
@NotNullByDefault
|
||||
RemovableDriveMonitor monitor = new UnixRemovableDriveMonitor() {
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] {dir.getPath()};
|
||||
}
|
||||
};
|
||||
return monitor;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.modem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
@@ -66,7 +65,6 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -78,14 +76,12 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
plugin.start();
|
||||
// A connection should be returned
|
||||
assertNotNull(plugin.createConnection(contactId));
|
||||
assertNotNull(plugin.createConnection(remote));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -105,7 +101,6 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -117,14 +112,12 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
plugin.start();
|
||||
// No connection should be returned
|
||||
assertNull(plugin.createConnection(contactId));
|
||||
assertNull(plugin.createConnection(remote));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -144,7 +137,6 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -156,8 +148,6 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(throwException(new IOException()));
|
||||
// resetModem()
|
||||
@@ -170,7 +160,7 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
}});
|
||||
plugin.start();
|
||||
// No connection should be returned
|
||||
assertNull(plugin.createConnection(contactId));
|
||||
assertNull(plugin.createConnection(remote));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,15 +238,15 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10004
|
||||
versionName "1.0.4"
|
||||
versionCode 10005
|
||||
versionName "1.0.5"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
resValue "string", "app_package", "org.briarproject.briar.android"
|
||||
resValue "string", "app_name", "Briar"
|
||||
buildConfigField "String", "GitHash",
|
||||
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
||||
buildConfigField "Long", "BuildTimestamp",
|
||||
"${getStdout(['git', 'log', '-n', '1', '--date=unix', '--format=%cd'], 0)}000L"
|
||||
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], 0)}000L"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -256,11 +256,13 @@ android {
|
||||
resValue "string", "app_name", "Briar Debug"
|
||||
shrinkResources false
|
||||
minifyEnabled true
|
||||
crunchPngs false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
release {
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
crunchPngs false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
@@ -276,10 +278,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
cruncherEnabled = false
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
warning 'MissingTranslation'
|
||||
warning 'ImpliedQuantity'
|
||||
|
||||
@@ -17,31 +17,32 @@ class AndroidDatabaseConfig implements DatabaseConfig {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidDatabaseConfig.class.getName());
|
||||
|
||||
private final File dir;
|
||||
private final File dbDir, keyDir;
|
||||
|
||||
@Nullable
|
||||
private volatile SecretKey key = null;
|
||||
@Nullable
|
||||
private volatile String nickname = null;
|
||||
|
||||
AndroidDatabaseConfig(File dir) {
|
||||
this.dir = dir;
|
||||
AndroidDatabaseConfig(File dbDir, File keyDir) {
|
||||
this.dbDir = dbDir;
|
||||
this.keyDir = keyDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean databaseExists() {
|
||||
// FIXME should not run on UiThread #620
|
||||
if (!dir.isDirectory()) {
|
||||
if (!dbDir.isDirectory()) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(dir.getAbsolutePath() + " is not a directory");
|
||||
LOG.info(dbDir.getAbsolutePath() + " is not a directory");
|
||||
return false;
|
||||
}
|
||||
File[] files = dir.listFiles();
|
||||
File[] files = dbDir.listFiles();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (files == null) {
|
||||
LOG.info("Could not list files in " + dir.getAbsolutePath());
|
||||
LOG.info("Could not list files in " + dbDir.getAbsolutePath());
|
||||
} else {
|
||||
LOG.info("Files in " + dir.getAbsolutePath() + ":");
|
||||
LOG.info("Files in " + dbDir.getAbsolutePath() + ":");
|
||||
for (File f : files) LOG.info(f.getName());
|
||||
}
|
||||
LOG.info("Database exists: " + (files != null && files.length > 0));
|
||||
@@ -51,10 +52,16 @@ class AndroidDatabaseConfig implements DatabaseConfig {
|
||||
|
||||
@Override
|
||||
public File getDatabaseDirectory() {
|
||||
File dir = this.dir;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Database directory: " + dir.getAbsolutePath());
|
||||
return dir;
|
||||
LOG.info("Database directory: " + dbDir.getAbsolutePath());
|
||||
return dbDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabaseKeyDirectory() {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Database key directory: " + keyDir.getAbsolutePath());
|
||||
return keyDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.StrictMode;
|
||||
|
||||
@@ -8,12 +9,22 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||
import org.briarproject.bramble.api.ui.UiCallback;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
@@ -23,14 +34,21 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
||||
|
||||
@@ -47,27 +65,9 @@ public class AppModule {
|
||||
}
|
||||
|
||||
private final Application application;
|
||||
private final UiCallback uiCallback;
|
||||
|
||||
public AppModule(Application application) {
|
||||
this.application = application;
|
||||
uiCallback = new UiCallback() {
|
||||
|
||||
@Override
|
||||
public int showChoice(String[] options, String... message) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String... message) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -76,25 +76,59 @@ public class AppModule {
|
||||
return application;
|
||||
}
|
||||
|
||||
@Provides
|
||||
UiCallback provideUICallback() {
|
||||
return uiCallback;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
DatabaseConfig provideDatabaseConfig(Application app) {
|
||||
//FIXME: StrictMode
|
||||
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskReads();
|
||||
StrictMode.allowThreadDiskWrites();
|
||||
File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
|
||||
File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
|
||||
File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE);
|
||||
StrictMode.setThreadPolicy(tp);
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
DatabaseConfig databaseConfig = new AndroidDatabaseConfig(dir);
|
||||
DatabaseConfig databaseConfig =
|
||||
new AndroidDatabaseConfig(dbDir, keyDir);
|
||||
return databaseConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
AndroidExecutor androidExecutor, SecureRandom random,
|
||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||
Application app, LocationUtils locationUtils, EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
scheduler, backoffFactory, appContext);
|
||||
Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return duplex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
DevConfig provideDevConfig(Application app, CryptoComponent crypto) {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -27,7 +23,6 @@ import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
@@ -35,7 +30,6 @@ import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||
|
||||
@@ -135,18 +129,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
Intent i = new Intent(ctx, ReblogActivity.class);
|
||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||
i.putExtra(POST_ID, item.getId().getBytes());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23 && !isSamsung7()) {
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation((Activity) ctx, layout,
|
||||
getTransitionName(item.getId()));
|
||||
ActivityCompat.startActivity(ctx, i,
|
||||
options.toBundle());
|
||||
} else {
|
||||
// work-around for android bug #224270
|
||||
// work-around for Samsung Android 7 bug #1007
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
ctx.startActivity(i);
|
||||
});
|
||||
|
||||
// comments
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface ConfigController {
|
||||
@Nullable
|
||||
String getEncryptedDatabaseKey();
|
||||
|
||||
void storeEncryptedDatabaseKey(String hex);
|
||||
boolean storeEncryptedDatabaseKey(String hex);
|
||||
|
||||
void deleteAccount(Context ctx);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.controller;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
@@ -9,12 +8,18 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ConfigControllerImpl implements ConfigController {
|
||||
@@ -23,8 +28,11 @@ public class ConfigControllerImpl implements ConfigController {
|
||||
Logger.getLogger(ConfigControllerImpl.class.getName());
|
||||
|
||||
private static final String PREF_DB_KEY = "key";
|
||||
private static final String DB_KEY_FILENAME = "db.key";
|
||||
private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
|
||||
|
||||
private final SharedPreferences briarPrefs;
|
||||
private final File dbKeyFile, dbKeyBackupFile;
|
||||
protected final DatabaseConfig databaseConfig;
|
||||
|
||||
@Inject
|
||||
@@ -32,22 +40,113 @@ public class ConfigControllerImpl implements ConfigController {
|
||||
DatabaseConfig databaseConfig) {
|
||||
this.briarPrefs = briarPrefs;
|
||||
this.databaseConfig = databaseConfig;
|
||||
File keyDir = databaseConfig.getDatabaseKeyDirectory();
|
||||
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
|
||||
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getEncryptedDatabaseKey() {
|
||||
String key = briarPrefs.getString(PREF_DB_KEY, null);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Got database key from preferences: " + (key != null));
|
||||
String key = getDatabaseKeyFromPreferences();
|
||||
if (key == null) key = getDatabaseKeyFromFile();
|
||||
else migrateDatabaseKeyToFile(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDatabaseKeyFromPreferences() {
|
||||
String key = briarPrefs.getString(PREF_DB_KEY, null);
|
||||
if (key == null) LOG.info("No database key in preferences");
|
||||
else LOG.info("Found database key in preferences");
|
||||
return key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDatabaseKeyFromFile() {
|
||||
String key = readDbKeyFromFile(dbKeyFile);
|
||||
if (key == null) {
|
||||
LOG.info("No database key in primary file");
|
||||
key = readDbKeyFromFile(dbKeyBackupFile);
|
||||
if (key == null) LOG.info("No database key in backup file");
|
||||
else LOG.warning("Found database key in backup file");
|
||||
} else {
|
||||
LOG.info("Found database key in primary file");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String readDbKeyFromFile(File f) {
|
||||
if (!f.exists()) {
|
||||
LOG.info("Key file does not exist");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(f), "UTF-8"));
|
||||
String key = reader.readLine();
|
||||
reader.close();
|
||||
return key;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateDatabaseKeyToFile(String key) {
|
||||
if (storeEncryptedDatabaseKey(key)) {
|
||||
if (briarPrefs.edit().remove(PREF_DB_KEY).commit())
|
||||
LOG.info("Database key migrated to file");
|
||||
else LOG.warning("Database key not removed from preferences");
|
||||
} else {
|
||||
LOG.warning("Database key not migrated to file");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public void storeEncryptedDatabaseKey(String hex) {
|
||||
LOG.info("Storing database key in preferences");
|
||||
briarPrefs.edit().putString(PREF_DB_KEY, hex).commit();
|
||||
public boolean storeEncryptedDatabaseKey(String hex) {
|
||||
LOG.info("Storing database key in file");
|
||||
// Create the directory if necessary
|
||||
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
|
||||
LOG.info("Created database key directory");
|
||||
// If only the backup file exists, rename it so we don't overwrite it
|
||||
if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
|
||||
if (dbKeyBackupFile.renameTo(dbKeyFile))
|
||||
LOG.info("Renamed old backup");
|
||||
else LOG.warning("Failed to rename old backup");
|
||||
}
|
||||
try {
|
||||
// Write to the backup file
|
||||
writeDbKeyToFile(hex, dbKeyBackupFile);
|
||||
LOG.info("Stored database key in backup file");
|
||||
// Delete the old primary file, if it exists
|
||||
if (dbKeyFile.exists()) {
|
||||
if (dbKeyFile.delete()) LOG.info("Deleted primary file");
|
||||
else LOG.warning("Failed to delete primary file");
|
||||
}
|
||||
// The backup file becomes the new primary
|
||||
if (dbKeyBackupFile.renameTo(dbKeyFile)) {
|
||||
LOG.info("Renamed backup file to primary");
|
||||
} else {
|
||||
LOG.warning("Failed to rename backup file to primary");
|
||||
return false; // Don't overwrite our only copy
|
||||
}
|
||||
// Write a second copy to the backup file
|
||||
writeDbKeyToFile(hex, dbKeyBackupFile);
|
||||
LOG.info("Stored second copy of database key in backup file");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDbKeyToFile(String key, File f) throws IOException {
|
||||
FileOutputStream out = new FileOutputStream(f);
|
||||
out.write(key.getBytes("UTF-8"));
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,22 +155,16 @@ public class ConfigControllerImpl implements ConfigController {
|
||||
SharedPreferences defaultPrefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs);
|
||||
AndroidUtils.logDataDirContents(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accountExists() {
|
||||
String hex = getEncryptedDatabaseKey();
|
||||
boolean exists = hex != null && databaseConfig.databaseExists();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Account exists: " + exists);
|
||||
return exists;
|
||||
return hex != null && databaseConfig.databaseExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accountSignedIn() {
|
||||
boolean signedIn = databaseConfig.getEncryptionKey() != null;
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Signed in: " + signedIn);
|
||||
return signedIn;
|
||||
return databaseConfig.getEncryptionKey() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ public class PasswordControllerImpl extends ConfigControllerImpl
|
||||
} else {
|
||||
String hex =
|
||||
encryptDatabaseKey(new SecretKey(key), newPassword);
|
||||
storeEncryptedDatabaseKey(hex);
|
||||
resultHandler.onResult(true);
|
||||
resultHandler.onResult(storeEncryptedDatabaseKey(hex));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
|
||||
@@ -103,14 +102,11 @@ public class SetupControllerImpl extends PasswordControllerImpl
|
||||
if (password == null) throw new IllegalStateException();
|
||||
cryptoExecutor.execute(() -> {
|
||||
LOG.info("Creating account");
|
||||
AndroidUtils.logDataDirContents(setupActivity);
|
||||
databaseConfig.setLocalAuthorName(authorName);
|
||||
SecretKey key = crypto.generateSecretKey();
|
||||
databaseConfig.setEncryptionKey(key);
|
||||
String hex = encryptDatabaseKey(key, password);
|
||||
storeEncryptedDatabaseKey(hex);
|
||||
LOG.info("Created account");
|
||||
AndroidUtils.logDataDirContents(setupActivity);
|
||||
resultHandler.onResult(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.support.v7.preference.PreferenceManager;
|
||||
import android.transition.Fade;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
@@ -45,11 +44,9 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
setContentView(R.layout.splash);
|
||||
|
||||
if (configController.accountSignedIn()) {
|
||||
LOG.info("Already signed in, not showing splash screen");
|
||||
startActivity(new Intent(this, OpenDatabaseActivity.class));
|
||||
finish();
|
||||
} else {
|
||||
LOG.info("Showing splash screen");
|
||||
new Handler().postDelayed(() -> {
|
||||
startNextActivity();
|
||||
supportFinishAfterTransition();
|
||||
@@ -67,7 +64,6 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
LOG.info("Expired");
|
||||
startActivity(new Intent(this, ExpiredActivity.class));
|
||||
} else {
|
||||
AndroidUtils.logDataDirContents(this);
|
||||
if (configController.accountExists()) {
|
||||
LOG.info("Account exists");
|
||||
startActivity(new Intent(this, OpenDatabaseActivity.class));
|
||||
@@ -80,10 +76,8 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void setPreferencesDefaults() {
|
||||
androidExecutor.runOnBackgroundThread(() -> {
|
||||
PreferenceManager.setDefaultValues(SplashScreenActivity.this,
|
||||
R.xml.panic_preferences, false);
|
||||
LOG.info("Finished setting panic preference defaults");
|
||||
});
|
||||
androidExecutor.runOnBackgroundThread(() ->
|
||||
PreferenceManager.setDefaultValues(SplashScreenActivity.this,
|
||||
R.xml.panic_preferences, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
||||
textInput.setListener(this);
|
||||
list = findViewById(R.id.list);
|
||||
layoutManager = new LinearLayoutManager(this);
|
||||
// FIXME pre-fetching messes with read state, find better solution #1289
|
||||
layoutManager.setItemPrefetchEnabled(false);
|
||||
list.setLayoutManager(layoutManager);
|
||||
adapter = createAdapter(layoutManager);
|
||||
list.setAdapter(adapter);
|
||||
|
||||
@@ -102,9 +102,11 @@
|
||||
<string name="show_onboarding">Hilfe anzeigen</string>
|
||||
<string name="fix">Behoben</string>
|
||||
<string name="help">Hilfe</string>
|
||||
<string name="sorry">Entschuldigung</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Du hast noch keine Kontakte\n\nTippe auf das +-Symbol um einen Kontakt hinzuzufügen</string>
|
||||
<string name="date_no_private_messages">Keine Nachrichten.</string>
|
||||
<string name="no_private_messages">Keine Nachrichten zum Anzeigen</string>
|
||||
<string name="message_hint">Nachricht eingeben</string>
|
||||
<string name="delete_contact">Kontakt löschen</string>
|
||||
<string name="dialog_title_delete_contact">Löschen des Kontakts bestätigen</string>
|
||||
@@ -208,6 +210,7 @@
|
||||
<string name="choose_forum_hint">Wähle einen Namen für dein Forum</string>
|
||||
<string name="create_forum_button">Forum erstellen</string>
|
||||
<string name="forum_created_toast">Forum wurde erstellt</string>
|
||||
<string name="no_forum_posts">Keine Beiträge zum Anzeigen</string>
|
||||
<string name="no_posts">Keine Beiträge</string>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d Beitrag</item>
|
||||
@@ -250,6 +253,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">Niemand</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Keine Blogbeiträge zum Anzeigen</string>
|
||||
<string name="read_more">weiterlesen</string>
|
||||
<string name="blogs_write_blog_post">Blogbeitrag erstellen</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Gib deinen Blogbeitrag ein</string>
|
||||
@@ -325,12 +329,16 @@
|
||||
<string name="notification_settings_title">Benachrichtigungen</string>
|
||||
<string name="notify_private_messages_setting_title">Private Nachrichten</string>
|
||||
<string name="notify_private_messages_setting_summary">Zeige Benachrichtigungen für private Nachrichten</string>
|
||||
<string name="notify_private_messages_setting_summary_26">Benachrichtigungen für private Nachrichten konfigurieren</string>
|
||||
<string name="notify_group_messages_setting_title">Gruppennachrichten</string>
|
||||
<string name="notify_group_messages_setting_summary">Benachrichtigungen für Gruppennachrichten anzeigen</string>
|
||||
<string name="notify_group_messages_setting_summary_26">Benachrichtigungen für Gruppennachrichten konfigurieren</string>
|
||||
<string name="notify_forum_posts_setting_title">Forenbeiträge</string>
|
||||
<string name="notify_forum_posts_setting_summary">Benachrichtigungen für Forenbeiträge anzeigen</string>
|
||||
<string name="notify_forum_posts_setting_summary_26">Benachrichtigungen für Forenbeiträge konfigurieren</string>
|
||||
<string name="notify_blog_posts_setting_title">Blogbeiträge</string>
|
||||
<string name="notify_blog_posts_setting_summary">Benachrichtigungen für Blogbeiträge anzeigen</string>
|
||||
<string name="notify_blog_posts_setting_summary_26">Benachrichtigungen für Blogbeiträge konfigurieren</string>
|
||||
<string name="notify_vibration_setting">Vibration</string>
|
||||
<string name="notify_lock_screen_setting_title">Sperrbildschirm</string>
|
||||
<string name="notify_lock_screen_setting_summary">Zeigt Benachrichtigungen auf dem Sperrbildschirm an</string>
|
||||
@@ -375,4 +383,5 @@
|
||||
<string name="permission_camera_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Hinzufügen von Kontakten erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
|
||||
<string name="permission_camera_denied_toast">Berechtigung für Kamera wurde nicht gewährt</string>
|
||||
<string name="qr_code">QR-Code</string>
|
||||
<string name="show_qr_code_fullscreen">QR-Code im Vollbildmodus anzeigen</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
<string name="setup_name_explanation">نام مستعارتان کنار هر مطلب شما قرار خواهد گرفت و بعد از ایجاد حساب کاربری امکان تغییر آن وجود ندارد.</string>
|
||||
<string name="setup_next">بعدی</string>
|
||||
<string name="setup_password_intro">یک رمز عبور انتخاب کنید</string>
|
||||
<string name="setup_password_explanation">حساب کاربری برایر شما به صورت رمزگذاری شده روی وسیله شما به جای حافظه ابری ذخیره شده است. اگر شما رمز عبور خود را فراموش کنید یا برایر را پاک کنید، راهی برای بازیابی حساب کاربری شما وجود نخواهد داشت.
|
||||
|
||||
یک رمز عبور طولانی انتخاب کنید که حدس آن سخت باشد، مثل چهار عبارت تصادفی یا ده لغت تصادفی با اعداد و نماد ها.</string>
|
||||
<string name="setup_doze_title">اتصال های پس زمینه</string>
|
||||
<string name="setup_doze_intro">برای دریافت پیام ها، برایر نیاز دارد تا در پس زمینه اتصال داشته باشد.</string>
|
||||
<string name="setup_doze_button">اجازه دادن به اتصالات</string>
|
||||
<string name="setup_doze_intro">برای دریافت پیام، برایر نیاز دارد تا در پس زمینه اتصال داشته باشد.</string>
|
||||
<string name="setup_doze_explanation">برای دریافت پیام، برایر نیاز دارد تا در پس زمینه اتصال داشته باشد. لطفا بهینه سازی باتری را غیر فعال کنید تا برایر بتواند به اتصال خود ادامه دهد.</string>
|
||||
<string name="setup_doze_button">دادن اجازه به اتصالات</string>
|
||||
<string name="choose_nickname">نام مستعار خود را انتخاب کنید</string>
|
||||
<string name="choose_password">رمز عبور خود را انتخاب کنید</string>
|
||||
<string name="confirm_password">رمز عبور خود را تایید کنید</string>
|
||||
@@ -20,19 +24,31 @@
|
||||
<string name="setup_huawei_text">لطفا روی دکمه زیر کلیک کنید و مطمئن شوید که از برایر در صفحه \"برنامه های محافظت شده\" محافظت می شود.</string>
|
||||
<string name="setup_huawei_button">حفاظت از برایر</string>
|
||||
<string name="setup_huawei_help">اگر برایر به فهرست برنامه های محافظت شده اضافه نشده، نمی تواند در پس زمینه مشغول به کار باشد.</string>
|
||||
<string name="warning_dozed">ناتوانی %s برای اجراء در پس زمینه</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">رمز عبور خود را وارد کنید:</string>
|
||||
<string name="try_again">رمز عبور اشتباه است، لطفا دوباره سعی کنید</string>
|
||||
<string name="sign_in_button">ورود</string>
|
||||
<string name="forgotten_password">رمز عبور را فراموش کرده ام</string>
|
||||
<string name="dialog_title_lost_password">رمز عبور گمشده</string>
|
||||
<string name="dialog_message_lost_password">حساب کاربری برایر شما به صورت رمزنگاری شده روی سیستم شما به جای حافظه ابری ذخیره شده است برای همین ما نمی توانیم رمز عبور شما را به صورت مجدد تنظیم کنیم. آیا مایل هستید تا حساب کاربری شما را پاک کنیم و دوباره از ابتدا شروع کنیم؟
|
||||
|
||||
اخطار: هویت های شما، مخاطبان شما و پیام های شما برای همیشه از بین خواهند رفت.</string>
|
||||
<string name="startup_failed_notification_title">برایر نمی تواند شروع به کار کند.</string>
|
||||
<string name="startup_failed_notification_text">برای اطلاعات بیشتر کلیک کنید</string>
|
||||
<string name="startup_failed_activity_title">خطا در شروع برایر</string>
|
||||
<string name="startup_failed_db_error">به دلایلی، دیتابیس برایر شما خراب شده و قابل اصلاح نیست. حساب کاربری شما، داده های شما و تمام مخاطبان شما از بین رفته اند. متاسفانه، شما یا باید برایر را دوباره نصب کنید یا یک حساب کاربری جدید با انتخاب \'رمز عبور ام را فراموش کرده ام\' انتخاب کنید.</string>
|
||||
<string name="startup_failed_data_too_old_error">حساب کاربری شما با یک نسخه قدیمی از این برنامه ایجاد شده است و به همین خاطربا این نسخه نمی تواند باز شود. شما باید نسخه قدیمی را دوباره نصب کنید یا یک حساب کاربری جدید با \"رمز عبور ام را فراموش کرده ام\" در پرامپت رمز عبور ایجاد کنید.</string>
|
||||
<string name="startup_failed_data_too_new_error">این نسخه از برنامه قدیمی می باشد. لطفا به آخرین نسخه از برنامه ارتقاء داده و دوباره سعی کنید.</string>
|
||||
<string name="startup_failed_service_error">برایر نمی تواند یک پلاگین ضروری را اجراء کند. نصب دوباره برایر معمولا این مشکل را حل میکند. هرچند، توجه داشته باشید که حساب کاربری و تمام داده های مرتبط با آن را از دست خواهید داد از آنجایی که برایر از هیچ سرور مرکزی برای ذخیره داده های شما استفاده نمی کند.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">این یک نسخه آزمایشی از برایر می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
|
||||
<item quantity="other">این یک نسخه آزمایشی از برایر می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
|
||||
</plurals>
|
||||
<string name="expiry_update">تاریخ اتمام آزمایش افزایش یافته است. حساب کاربری شما در %d روز آینده به پایان می رسد.</string>
|
||||
<string name="expiry_date_reached">این نرم افزار منقضی شده است.
|
||||
|
||||
بابت تست از شما سپاسگزاریم.</string>
|
||||
<string name="download_briar">برای اینکه به استفاده خود از برایر ادامه دهید، لطفا نسخه ۱.۰ را دانلود کنید.</string>
|
||||
<string name="create_new_account">لازم است تا یک حساب کاربری جدید ایجاد کنید، می توانید از نام مستعار یکسان برای حساب های کاربری استفاده کنید.</string>
|
||||
<string name="download_briar_button">دانلود برایر 1.0</string>
|
||||
@@ -52,6 +68,7 @@
|
||||
<string name="transport_bt">بلوتوث</string>
|
||||
<string name="transport_lan">وای فای</string>
|
||||
<!--Notifications-->
|
||||
<string name="ongoing_notification_title">وارد برایر شد</string>
|
||||
<string name="ongoing_notification_text">برای باز کردن برایر کلیک کنید.</string>
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">%dپیام خصوصی جدید</item>
|
||||
@@ -88,10 +105,14 @@
|
||||
<string name="no_data">داده ای موجود نمی باشد</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>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">مخاطبی برای نشان دادن وجود ندارد
|
||||
|
||||
روی علامت + برای افزودن مخاطب کلیک کنید</string>
|
||||
<string name="date_no_private_messages">هیچ پیامی موجود نیست</string>
|
||||
<string name="no_private_messages">هیچ پیامی برای نشان دادن وجود ندارد</string>
|
||||
<string name="message_hint">نوشتن پیام</string>
|
||||
@@ -106,9 +127,22 @@
|
||||
<string name="continue_button">ادامه</string>
|
||||
<string name="connection_failed">اتصال ناموفق بود</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_already_exists">مخاطب %s از قبل وجود دارد</string>
|
||||
<string name="contact_exchange_failed">تبادل مخاطب با خطا مواجه شد</string>
|
||||
<string name="qr_code_invalid">کد QR نامعتبر می باشد</string>
|
||||
<string name="qr_code_unsupported">کد QR که شما سعی دارید اسکن کنید متعلق به یک نسخه قدیمی از %s میباشد که دیگر پشتیبانی نمی شود.
|
||||
|
||||
لطفا مطمئن شوید که هردوی شما از آخرین نسخه استفاده میکنید و دوباره امتحان کنید.</string>
|
||||
<string name="camera_error">خطای دوربین</string>
|
||||
<string name="connecting_to_device">اتصال به دستگاهu2026\</string>
|
||||
<string name="authenticating_with_device">تصدیق سازی با دستگاه u2026\</string>
|
||||
<string name="connection_aborted_local">اتصال قطع شد! این میتواند نشان دهنده این باشد که شخصی قصد دارد در اتصال شما اختلال ایجاد کند</string>
|
||||
<string name="connection_aborted_remote">اتصال توسط مخاطب شما بی نتیجه ماند! این ممکن است نشان دهنده این باشد که شخصی سعی دارد تا در اتصال شما اختلال ایجاد کند</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">معرفی مخاطبان</string>
|
||||
<string name="introduction_onboarding_text">شما می توانید مخاطبان خود را به یکدیگر معرفی کنید، در این صورت دیگر نیازی به دیدار حضوری برای اتصال روی برایر نمی باشد.</string>
|
||||
<string name="introduction_activity_title">انتخاب مخاطب</string>
|
||||
<string name="introduction_not_possible">شما همین الان یک معرفی در مرحله انجام با این مخاطبان دارید. لطفا اجازه دهید تا این معرفی به پایان برسد. اگر شما و یا مخاطبانتان به ندرت آنلاین هستید، ممکن است کمی زمان ببرد.</string>
|
||||
@@ -118,6 +152,9 @@
|
||||
<string name="introduction_sent">معرفی شما فرستاده شد.</string>
|
||||
<string name="introduction_error">خطایی در معرفی کردن رخ داده است.</string>
|
||||
<string name="introduction_response_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_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>
|
||||
@@ -130,6 +167,9 @@
|
||||
<item quantity="other">%d مخاطب جدید افزوده شد.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">هیچ گروهی برای نمایش وجود ندارد
|
||||
|
||||
روی آیکون + برای ایجاد یک گروه کلیک کنید، یا از مخاطبان خود بخواهید تا گروهی را با شما به اشتراک بگذارند</string>
|
||||
<string name="groups_created_by">ایجاد شده توسط %s</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d پیام</item>
|
||||
@@ -155,13 +195,24 @@
|
||||
<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">آیا از انحلال این گروه اطمینان دارید؟
|
||||
|
||||
سایر اعضاء قادر نخواهند بود تا مکالمات خود را ادامه دهند و ممکن است آخرین پیام ها را دریافت نکنند.</string>
|
||||
<string name="groups_dissolve_button">انحلال</string>
|
||||
<string name="groups_dissolved_dialog_title">گروه انحلال یافت</string>
|
||||
<string name="groups_dissolved_dialog_message">ایجاد کننده این گروه آن را منحل کرده است.
|
||||
|
||||
شما از این به بعد قادر نخواهید بود پیام های دیگری در گروه بنویسید و ممکن است تمام پست هایی که نوشته شده اند را دریافت نکنید.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<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>
|
||||
<string name="groups_invitations_declined">دعوت نامه گروه رد شد</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d دعوتنامه سرگشاده</item>
|
||||
<item quantity="other">%d دعوت نامه سرگشاده</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">شما دعوت نامه گروه از %s را پذیرفتید.</string>
|
||||
<string name="groups_invitations_response_declined_sent">شما دعوت نامه گروه از %s را رد کردید.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%sدعوت نامه گروه را پذیرفت.</string>
|
||||
@@ -169,7 +220,17 @@
|
||||
<string name="sharing_status_groups">فقط سازنده گروه می تواند اعضای جدید را به گروه دعوت کند. در پایین تمام اعضای فعلی گروه آمده است.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">نشان دادن مخاطبان</string>
|
||||
<string name="groups_reveal_dialog_message">شما میتوانید مخاطبان را به تمام اعضای فعلی و آینده این گروه نشان دهید.
|
||||
|
||||
نشان دادن مخاطبان اتصال شما به گروه را سریع تر و قابل اطمینان تر میکند، چراکه میتوانید با مخاطبان نشان داده شده ارتباط برقرار کنید حتی در موقعی که گروه آفلاین می باشد.</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="create_forum_title">ایجاد فروم</string>
|
||||
<string name="choose_forum_hint">انتخاب یک نام برای فروم</string>
|
||||
<string name="create_forum_button">ایجاد فروم</string>
|
||||
@@ -180,28 +241,48 @@
|
||||
<item quantity="one">%d پست</item>
|
||||
<item quantity="other">%d پست</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">ثبت فروم پست شد</string>
|
||||
<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">آیا اطمینان دارید که میخواهید این فروم را ترک کنید؟
|
||||
|
||||
هر مخاطبی که این فروم را با آنها به اشتراک گذاشته اید ممکن است آپدیت دیگری دریافت نکنند.</string>
|
||||
<string name="dialog_button_leave">ترک</string>
|
||||
<string name="forum_left_toast">ترک فروم</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">اشتراک گذاری فروم</string>
|
||||
<string name="contacts_selected">مخاطبان انتخاب شدند</string>
|
||||
<string name="activity_share_toolbar_header">انتخاب مخاطبان</string>
|
||||
<string name="no_contacts_selector">هیچ مخاطبی برای نمایش دادن وجود ندارد
|
||||
|
||||
لطفا بعد از افزودن مخاطب برگردید.</string>
|
||||
<string name="forum_shared_snackbar">فروم با مخاطبان انتخاب شده به اشتراک گذاشته شد</string>
|
||||
<string name="forum_share_message">افزودن یک پیام (اختیاری)</string>
|
||||
<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">شما دعوت به این فروم را پذیرفته بودید.
|
||||
|
||||
پذیرفتن دعوت نامه های بیشتر باعث می شود تا اتصال شما به فروم سریع تر و قابل اطمینان تر شود.</string>
|
||||
<string name="forum_joined_toast">عضو فروم شد</string>
|
||||
<string name="forum_declined_toast">دعوت نامه رد شد</string>
|
||||
<string name="shared_by_format">به اشتراک گذاشته شده توسط %s</string>
|
||||
<string name="forum_invitation_already_sharing">در حال به اشتراک گذاری</string>
|
||||
<string name="forum_invitation_response_accepted_sent">شما دعوت نامه فروم از %s را پذیرفتید.</string>
|
||||
<string name="forum_invitation_response_declined_sent">شما دعوت نامه فروم از %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="shared_with">به اشتراک گذاشته شده با %1$d (%2$d نفر آنلاین)</string>
|
||||
<plurals name="forums_shared">
|
||||
<item quantity="one"> %d فروم به اشتراک گذاشته شده توسط مخاطبان</item>
|
||||
<item quantity="other">%d فروم به اشتراک گذاشته شده توسط مخاطبان</item>
|
||||
</plurals>
|
||||
<string name="nobody">هیچکس</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">هیچ پستی برای نشان دادن وجود ندارد</string>
|
||||
@@ -211,12 +292,25 @@
|
||||
<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_scroll_to">حرکت به</string>
|
||||
<string name="blogs_feed_empty_state">هیچ پستی برای نشان دادن وجود ندارد
|
||||
|
||||
پست های مخاطبان و بلاگ هایی که مشترک آن ها هستید اینجا نشان داده خواهند شد
|
||||
|
||||
روی آیکون خودکار برای ایجاد یک پست کلیک کنید</string>
|
||||
<string name="blogs_remove_blog">حذف بلاگ</string>
|
||||
<string name="blogs_remove_blog_dialog_message">آیا اطمینان دارید که میخواهید این بلاگ را حذف کنید؟
|
||||
|
||||
پست ها از روی دستگاه شما حذف خواهند شد اما از روی دستگاه سایر افراد حذف نخواهد شد.
|
||||
|
||||
هر مخاطبی که شما این بلاگ را با آن ها به اشتراک گذاشته اید ممکن است از این به بعد آپدیتی دریافت نکند.</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>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">به اشتراک گذاری بلاگ</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>
|
||||
@@ -226,8 +320,12 @@
|
||||
<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_declined_toast">دعوت نامه رد شد</string>
|
||||
<string name="sharing_status_blog">هرکسی که به اشتراک یک بلاگ در بیاید می تواند آن را با مخاطبان خود به اشتراک بگذارد. شما این بلاگ را با مخاطبان زیر به اشتراک میگذارید. ممکن است مشترکان دیگری هم باشند که شما نتوانید آن ها را ببینید.</string>
|
||||
<!--RSS Feeds-->
|
||||
<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_error">متاسفیم! وارد کردن خوراک شما با خطا مواجه شده است.</string>
|
||||
<string name="blogs_rss_feeds_manage">مدیریت خوراک های RSS</string>
|
||||
@@ -235,8 +333,16 @@
|
||||
<string name="blogs_rss_feeds_manage_author">نویسنده:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">آخرین به روز رسانی:</string>
|
||||
<string name="blogs_rss_remove_feed">حذف فید</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">آیا مطمئن هستید که میخواهید این خوراک را حذف کنید؟
|
||||
|
||||
پست ها از دستگاه شما پاک خواهند شد اما روی دستگاه سایر افراد باقی خواهند ماند
|
||||
|
||||
هر مخاطبی که با آن این خوراک را به اشتراک گذاشته اید ممکن است دیگر آپدیت دریافت نکند.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">حذف</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">خوراک نمی تواند پاک شود!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">هیچ خوراک RSS برای نمایش وجود ندارد
|
||||
|
||||
برای وارد کردن خوراک روی آیکون + کلیک کنید</string>
|
||||
<string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">شبکه ها</string>
|
||||
@@ -254,24 +360,36 @@
|
||||
<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_title">دکمه هراس</string>
|
||||
<string name="panic_setting_hint">تنظیم نحوه واکنش برایر هنگام فعال سازی دکمه هراس</string>
|
||||
<string name="panic_app_setting_title">برنامه دکمه هراس</string>
|
||||
<string name="unknown_app">یک برنامه ناشناخته</string>
|
||||
<string name="panic_app_setting_summary">هیچ برنامه تنظیم نشده است</string>
|
||||
<string name="panic_app_setting_none">هیچکدام</string>
|
||||
<string name="dialog_title_connect_panic_app">تایید برنامه هراس</string>
|
||||
<string name="dialog_message_connect_panic_app">آیا مطمئن هستید که میخواهید به %1$s اجازه دهید تا باعث عملیات مخرب دکمه هراس بشود؟</string>
|
||||
<string name="lock_setting_title">خروج</string>
|
||||
<string name="lock_setting_summary">در صورت کلیک بر روی کلید هراس از برایر خارج شو</string>
|
||||
<string name="purge_setting_title">حذف حساب کاربری</string>
|
||||
<string name="purge_setting_summary">پاک کردن حساب کاربری برایر شما در صورتی که دکمه هراس فشار داده شود. اخطار: این باعث پاک شدن دائمی تمام هویت ها، مخاطبان و پیام های شما خواهد شد</string>
|
||||
<string name="uninstall_setting_title">پاک کردن برایر</string>
|
||||
<string name="uninstall_setting_summary">این نیازمند تایید دستی در موعد هراس میباشد</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">نوتیفیکیشن ها</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_forum_posts_setting_title">پست های فروم</string>
|
||||
<string name="notify_forum_posts_setting_summary">نمایش هشدار برای پست های فروم</string>
|
||||
<string name="notify_group_messages_setting_summary_26">تنظیم هشدار برای پیام های گروه</string>
|
||||
<string name="notify_forum_posts_setting_title">پست های تالار گفتمان</string>
|
||||
<string name="notify_forum_posts_setting_summary">نمایش هشدار برای پست های تالار گفتمان</string>
|
||||
<string name="notify_forum_posts_setting_summary_26">تنظیم هشدار برای پست های تالار گفتمان</string>
|
||||
<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_lock_screen_setting_title">قفل صفحه</string>
|
||||
<string name="notify_lock_screen_setting_summary">نمایش نوتیفیکشن روی صفحه قفل</string>
|
||||
<string name="notify_sound_setting">صدا</string>
|
||||
@@ -297,6 +415,8 @@
|
||||
<string name="describe_crash">توضیح دهید چه اتفاقی افتاد (اختیاری)</string>
|
||||
<string name="enter_feedback">بازخورد خود را وارد کنید</string>
|
||||
<string name="optional_contact_email">آدرس ایمیل شما (اختیاری)</string>
|
||||
<string name="include_debug_report_crash">قرار دادن داده های ناشناس مربوط به خرابی</string>
|
||||
<string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string>
|
||||
<string name="could_not_load_report_data">امکان بارگذاری داده های گزارش وجود ندارد.</string>
|
||||
<string name="send_report">ارسال گزارش</string>
|
||||
<string name="close">بستن</string>
|
||||
@@ -304,9 +424,20 @@
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">خروج از برایر...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">قرارگیری صفحه شناسایی شد</string>
|
||||
<string name="screen_filter_body">برنامه ای دیگری روی برایر قرار گرفته است. برای حفاظت از امنیت شما، برایر پاسخگو به لمس های صفحه نمایش تا زمانی که برنامه دیگری بالای آن قرار دارد نخواهد بود.
|
||||
|
||||
این برنامه ها ممکن است روی برایر قرار گرفته باشند:
|
||||
|
||||
%1$s</string>
|
||||
<string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی برایر قرار بگیرند</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">دسترسی به دوربین</string>
|
||||
<string name="permission_camera_request_body">برای اسکن کردن کد QR دسترسی به دوربین لازم است.</string>
|
||||
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
|
||||
|
||||
لطفا اجازه دسترسی را بدهید.</string>
|
||||
<string name="permission_camera_denied_toast">اجازه دسترسی به دوربین پذیرفته نشد</string>
|
||||
<string name="qr_code">کد QR</string>
|
||||
<string name="show_qr_code_fullscreen">نمایش کد QR به صورت فول اسکرین</string>
|
||||
</resources>
|
||||
|
||||
@@ -42,25 +42,25 @@
|
||||
<plurals name="private_message_notification_text">
|
||||
<item quantity="one">הודעה פרטית חדשה.</item>
|
||||
<item quantity="two">%d הודעות פריטות חדשות.</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d הודעות פריטות חדשות.</item>
|
||||
<item quantity="other">%d הודעות פריטות חדשות.</item>
|
||||
</plurals>
|
||||
<plurals name="group_message_notification_text">
|
||||
<item quantity="one">הודעה קבוצתית חדשה.</item>
|
||||
<item quantity="two">%d הודעות קבוצתיות חדשות.</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d הודעות קבוצתיות חדשות.</item>
|
||||
<item quantity="other">%d הודעות קבוצתיות חדשות.</item>
|
||||
</plurals>
|
||||
<plurals name="forum_post_notification_text">
|
||||
<item quantity="one">פוסט חדש בפורומים.</item>
|
||||
<item quantity="two">%d פוסטים חדשים בפורומים.</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d פוסטים חדשים בפורומים.</item>
|
||||
<item quantity="other">%d פוסטים חדשים בפורומים.</item>
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">פוסט חדש בבלוגים.</item>
|
||||
<item quantity="two">%d פוסטים חדשים בבלוגים.</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d פוסטים חדשים בבלוגים.</item>
|
||||
<item quantity="other">%d פוסטים חדשים בבלוגים.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
@@ -130,7 +130,7 @@
|
||||
<plurals name="introduction_notification_text">
|
||||
<item quantity="one">נוסף איש קשר חדש.</item>
|
||||
<item quantity="two">נוספו %d אנשי קשר חדשים.</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">נוספו %d אנשי קשר חדשים.</item>
|
||||
<item quantity="other">נוספו %d אנשי קשר חדשים.</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
@@ -138,7 +138,7 @@
|
||||
<plurals name="messages">
|
||||
<item quantity="one">הודעה %d</item>
|
||||
<item quantity="two">%d הודעות</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d הודעות</item>
|
||||
<item quantity="other">%d הודעות</item>
|
||||
</plurals>
|
||||
<string name="groups_group_is_empty">קבוצה זו הינה ריקה</string>
|
||||
@@ -178,7 +178,7 @@
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">הזמנה קבוצתית פתוחה %d</item>
|
||||
<item quantity="two">%dהזמנות קבוצתית פתוחות </item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%dהזמנות קבוצתית פתוחות </item>
|
||||
<item quantity="other">%dהזמנות קבוצתית פתוחות </item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">הסכמתם להזמנה הקבוצתית מ- %s.</string>
|
||||
@@ -204,7 +204,7 @@
|
||||
<plurals name="posts">
|
||||
<item quantity="one">פוסט %d</item>
|
||||
<item quantity="two">%d פוסטים</item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="many">%d פוסטים</item>
|
||||
<item quantity="other">%d פוסטים</item>
|
||||
</plurals>
|
||||
<string name="forum_new_entry_posted">הפוסט נוצר</string>
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<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_declined_sent">您已谢绝与 %1$s 建立联系</string>
|
||||
<string name="introduction_response_accepted_received">%1$s 接受与 %2$s 建立联系</string>
|
||||
<string name="introduction_response_declined_received">%1$s 谢绝与 %2$s 建立联系</string>
|
||||
@@ -149,6 +150,7 @@
|
||||
<item quantity="other">已添加 %d 位新联系人。</item>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">没有群组\n\n点击 + 号创建群聊,或者让您的联系人分享群组给您</string>
|
||||
<string name="groups_created_by">由 %s 创建</string>
|
||||
<plurals name="messages">
|
||||
<item quantity="other">%d 条消息。</item>
|
||||
@@ -199,6 +201,7 @@
|
||||
<string name="groups_reveal_visible_revealed_by_contact">联系人关系对群聊可见 (由 %s 设定)</string>
|
||||
<string name="groups_reveal_invisible">联系人关系对群聊不可见</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">没有论坛\n\n点击 + 号来创建一个,或者让您的联系人分享一个论坛给您</string>
|
||||
<string name="create_forum_title">创建论坛</string>
|
||||
<string name="choose_forum_hint">为论坛命名</string>
|
||||
<string name="create_forum_button">创建论坛</string>
|
||||
@@ -214,17 +217,23 @@
|
||||
<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_button_leave">退出</string>
|
||||
<string name="forum_left_toast">已退出论坛</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">分享论坛</string>
|
||||
<string name="contacts_selected">已选择联系人</string>
|
||||
<string name="activity_share_toolbar_header">选择联系人</string>
|
||||
<string name="no_contacts_selector">没有联系人\n\n请添加联系人后再来</string>
|
||||
<string name="forum_shared_snackbar">论坛已分享给选中的联系人</string>
|
||||
<string name="forum_share_message">添加一句话 (选填)</string>
|
||||
<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_joined_toast">已加入论坛</string>
|
||||
<string name="forum_declined_toast">邀请已拒绝</string>
|
||||
<string name="shared_by_format">由 %s 分享</string>
|
||||
<string name="forum_invitation_already_sharing">已在分享</string>
|
||||
<string name="forum_invitation_response_accepted_sent">您接受了来自 %s的论坛邀请</string>
|
||||
@@ -242,12 +251,16 @@
|
||||
<string name="blogs_other_blog_empty_state">尚无帖子可供展示</string>
|
||||
<string name="read_more">阅读更多</string>
|
||||
<string name="blogs_write_blog_post">写博文</string>
|
||||
<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_scroll_to">滑动至</string>
|
||||
<string name="blogs_feed_empty_state">没有文章\n\n此处显示您好友的博文或您订阅的文章\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_blog_removed">博客已删除</string>
|
||||
<string name="blogs_reblog_comment_hint">添加评论 (选填)</string>
|
||||
<string name="blogs_reblog_button">转载</string>
|
||||
<!--Blog Sharing-->
|
||||
@@ -262,6 +275,8 @@
|
||||
<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_declined_toast">邀请已拒绝</string>
|
||||
<string name="sharing_status_blog">任何订阅博客的人都可以将它分享给他的联系人。您正在将该博客分享给下列联系人。可能有其他您所不可见的订阅者存在。</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">导入 RSS 订阅</string>
|
||||
@@ -273,8 +288,10 @@
|
||||
<string name="blogs_rss_feeds_manage_author">作者:</string>
|
||||
<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_feeds_manage_delete_error">该订阅无法被删除!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">没有订阅内容\n\n点击 + 号导入一个 RSS 信息流</string>
|
||||
<string name="blogs_rss_feeds_manage_error">加载订阅时出错。请稍候再试。</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">网络</string>
|
||||
@@ -311,12 +328,16 @@
|
||||
<string name="notification_settings_title">消息提醒</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>
|
||||
<string name="notify_forum_posts_setting_title">论坛帖子</string>
|
||||
<string name="notify_forum_posts_setting_summary">显示论坛帖子通知</string>
|
||||
<string name="notify_forum_posts_setting_summary_26">配置论坛文章通知</string>
|
||||
<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_lock_screen_setting_title">锁定屏幕</string>
|
||||
<string name="notify_lock_screen_setting_summary">在锁定屏幕上显示通知</string>
|
||||
@@ -360,4 +381,6 @@
|
||||
<string name="permission_camera_request_body">Briar 需要获得相机权限以扫描二维码。</string>
|
||||
<string name="permission_camera_denied_body">您拒绝了相机权限,而添加联系人需要使用相机\n\n请考虑授予相机权限。</string>
|
||||
<string name="permission_camera_denied_toast">未授予相机权限</string>
|
||||
<string name="qr_code">二维码</string>
|
||||
<string name="show_qr_code_fullscreen">全屏显示二维码</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestDatabaseKeyUtils {
|
||||
|
||||
public static void storeDatabaseKey(File f, String hex) throws IOException {
|
||||
f.getParentFile().mkdirs();
|
||||
FileOutputStream out = new FileOutputStream(f);
|
||||
out.write(hex.getBytes("UTF-8"));
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String loadDatabaseKey(File f) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(f), "UTF-8"));
|
||||
String hex = reader.readLine();
|
||||
reader.close();
|
||||
return hex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package org.briarproject.briar.android.controller;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
|
||||
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
|
||||
|
||||
public class ConfigControllerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final SharedPreferences prefs =
|
||||
context.mock(SharedPreferences.class);
|
||||
private final DatabaseConfig databaseConfig =
|
||||
context.mock(DatabaseConfig.class);
|
||||
private final Editor editor = context.mock(Editor.class);
|
||||
|
||||
private final byte[] encryptedKey = getRandomBytes(123);
|
||||
private final String encryptedKeyHex = toHexString(encryptedKey);
|
||||
private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123));
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File keyDir = new File(testDir, "key");
|
||||
private final File keyFile = new File(keyDir, "db.key");
|
||||
private final File keyBackupFile = new File(keyDir, "db.key.bak");
|
||||
|
||||
@Test
|
||||
public void testDbKeyIsMigratedFromPreferencesToFile() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(prefs).getString("key", null);
|
||||
will(returnValue(encryptedKeyHex));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
oneOf(prefs).edit();
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).remove("key");
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).commit();
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(prefs).getString("key", null);
|
||||
will(returnValue(null));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyFile, encryptedKeyHex);
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
|
||||
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDbKeyIsLoadedFromBackupFile() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(prefs).getString("key", null);
|
||||
will(returnValue(null));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
|
||||
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDbKeyIsNullIfNotFound() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(prefs).getString("key", null);
|
||||
will(returnValue(null));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertNull(c.getEncryptedDatabaseKey());
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoringDbKeyOverwritesPrimary() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyFile, oldEncryptedKeyHex);
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
|
||||
ConfigController c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoringDbKeyOverwritesBackup() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex);
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
|
||||
ConfigController c = new ConfigControllerImpl(prefs,
|
||||
databaseConfig);
|
||||
|
||||
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
|
||||
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,23 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
|
||||
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
|
||||
|
||||
public class PasswordControllerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -26,62 +35,90 @@ public class PasswordControllerImplTest extends BrambleMockTestCase {
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final PasswordStrengthEstimator estimator =
|
||||
context.mock(PasswordStrengthEstimator.class);
|
||||
private final SharedPreferences.Editor editor =
|
||||
context.mock(SharedPreferences.Editor.class);
|
||||
|
||||
private final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
|
||||
private final String oldPassword = "some.old.pass";
|
||||
private final String newPassword = "some.new.pass";
|
||||
private final String oldEncryptedHex = "010203";
|
||||
private final String newEncryptedHex = "020304";
|
||||
private final byte[] oldEncryptedBytes = new byte[] {1, 2, 3};
|
||||
private final byte[] newEncryptedBytes = new byte[] {2, 3, 4};
|
||||
private final byte[] keyBytes = getSecretKey().getBytes();
|
||||
private final byte[] oldEncryptedKey = getRandomBytes(123);
|
||||
private final byte[] newEncryptedKey = getRandomBytes(123);
|
||||
private final byte[] key = getSecretKey().getBytes();
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File keyDir = new File(testDir, "key");
|
||||
private final File keyFile = new File(keyDir, "db.key");
|
||||
private final File keyBackupFile = new File(keyDir, "db.key.bak");
|
||||
|
||||
@Test
|
||||
public void testChangePasswordReturnsTrue() {
|
||||
public void testChangePasswordReturnsTrue() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Look up the encrypted DB key
|
||||
oneOf(briarPrefs).getString("key", null);
|
||||
will(returnValue(oldEncryptedHex));
|
||||
will(returnValue(null));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
// Decrypt and re-encrypt the key
|
||||
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
|
||||
will(returnValue(keyBytes));
|
||||
oneOf(crypto).encryptWithPassword(keyBytes, newPassword);
|
||||
will(returnValue(newEncryptedBytes));
|
||||
// Store the re-encrypted key
|
||||
oneOf(briarPrefs).edit();
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).putString("key", newEncryptedHex);
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).commit();
|
||||
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
|
||||
will(returnValue(key));
|
||||
oneOf(crypto).encryptWithPassword(key, newPassword);
|
||||
will(returnValue(newEncryptedKey));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
|
||||
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
|
||||
|
||||
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
|
||||
databaseConfig, cryptoExecutor, crypto, estimator);
|
||||
|
||||
AtomicBoolean capturedResult = new AtomicBoolean(false);
|
||||
p.changePassword(oldPassword, newPassword, capturedResult::set);
|
||||
assertTrue(capturedResult.get());
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
|
||||
assertEquals(toHexString(newEncryptedKey),
|
||||
loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
|
||||
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Look up the encrypted DB key
|
||||
oneOf(briarPrefs).getString("key", null);
|
||||
will(returnValue(oldEncryptedHex));
|
||||
will(returnValue(null));
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
// Try to decrypt the key - the password is wrong
|
||||
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
|
||||
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
|
||||
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
|
||||
|
||||
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
|
||||
databaseConfig, cryptoExecutor, crypto, estimator);
|
||||
|
||||
AtomicBoolean capturedResult = new AtomicBoolean(true);
|
||||
p.changePassword(oldPassword, newPassword, capturedResult::set);
|
||||
assertFalse(capturedResult.get());
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(toHexString(oldEncryptedKey), loadDatabaseKey(keyFile));
|
||||
assertEquals(toHexString(oldEncryptedKey),
|
||||
loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.After;
|
||||
@@ -19,10 +18,17 @@ import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
|
||||
|
||||
public class SetupControllerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -33,18 +39,18 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final PasswordStrengthEstimator estimator =
|
||||
context.mock(PasswordStrengthEstimator.class);
|
||||
private final SharedPreferences.Editor editor =
|
||||
context.mock(SharedPreferences.Editor.class);
|
||||
private final SetupActivity setupActivity;
|
||||
|
||||
private final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
|
||||
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final String password = "some.strong.pass";
|
||||
private final String encryptedHex = "010203";
|
||||
private final byte[] encryptedBytes = new byte[] {1, 2, 3};
|
||||
private final byte[] encryptedKey = getRandomBytes(123);
|
||||
private final SecretKey key = getSecretKey();
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File keyDir = new File(testDir, "key");
|
||||
private final File keyFile = new File(keyDir, "db.key");
|
||||
private final File keyBackupFile = new File(keyDir, "db.key.bak");
|
||||
|
||||
public SetupControllerImplTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
@@ -53,7 +59,7 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public void testCreateAccount() {
|
||||
public void testCreateAccount() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Allow the contents of the data directory to be logged
|
||||
allowing(setupActivity).getApplicationInfo();
|
||||
@@ -76,15 +82,15 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
|
||||
oneOf(databaseConfig).setEncryptionKey(key);
|
||||
// Encrypt the key with the password
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
|
||||
will(returnValue(encryptedBytes));
|
||||
will(returnValue(encryptedKey));
|
||||
// Store the encrypted key
|
||||
oneOf(briarPrefs).edit();
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).putString("key", encryptedHex);
|
||||
will(returnValue(editor));
|
||||
oneOf(editor).commit();
|
||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||
will(returnValue(keyDir));
|
||||
}});
|
||||
|
||||
assertFalse(keyFile.exists());
|
||||
assertFalse(keyBackupFile.exists());
|
||||
|
||||
SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
|
||||
databaseConfig, cryptoExecutor, crypto, estimator);
|
||||
s.setSetupActivity(setupActivity);
|
||||
@@ -94,10 +100,15 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
|
||||
s.setPassword(password);
|
||||
s.createAccount(result -> called.set(true));
|
||||
assertTrue(called.get());
|
||||
|
||||
assertTrue(keyFile.exists());
|
||||
assertTrue(keyBackupFile.exists());
|
||||
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
|
||||
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +92,27 @@ class IntroducerProtocolEngine
|
||||
throw new UnsupportedOperationException(); // Invalid in this role
|
||||
}
|
||||
|
||||
IntroducerSession onAbortAction(Transaction txn, IntroducerSession s)
|
||||
IntroducerSession onIntroduceeRemoved(Transaction txn,
|
||||
Introducee remainingIntroducee, IntroducerSession session)
|
||||
throws DbException {
|
||||
return abort(txn, s);
|
||||
// abort session
|
||||
IntroducerSession s = abort(txn, session);
|
||||
// reset information for introducee that was removed
|
||||
Introducee introduceeA, introduceeB;
|
||||
if (remainingIntroducee.author.equals(s.getIntroduceeA().author)) {
|
||||
introduceeA = s.getIntroduceeA();
|
||||
introduceeB =
|
||||
new Introducee(s.getSessionId(), s.getIntroduceeB().groupId,
|
||||
s.getIntroduceeB().author);
|
||||
} else if (remainingIntroducee.author
|
||||
.equals(s.getIntroduceeB().author)) {
|
||||
introduceeA =
|
||||
new Introducee(s.getSessionId(), s.getIntroduceeA().groupId,
|
||||
s.getIntroduceeA().author);
|
||||
introduceeB = s.getIntroduceeB();
|
||||
} else throw new DbException();
|
||||
return new IntroducerSession(s.getSessionId(), s.getState(),
|
||||
s.getRequestTimestamp(), introduceeA, introduceeB);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -79,7 +79,7 @@ class IntroducerSession extends Session<IntroducerState> {
|
||||
i.lastLocalMessageId, remoteMessageId);
|
||||
}
|
||||
|
||||
private Introducee(SessionId sessionId, GroupId groupId,
|
||||
Introducee(SessionId sessionId, GroupId groupId,
|
||||
Author author) {
|
||||
this(sessionId, groupId, author, -1, null, null);
|
||||
}
|
||||
|
||||
@@ -555,7 +555,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
||||
IntroducerSession s, MessageId storageId, Introducee i,
|
||||
LocalAuthor localAuthor) throws DbException {
|
||||
if (db.containsContact(txn, i.author.getId(), localAuthor.getId())) {
|
||||
IntroducerSession session = introducerEngine.onAbortAction(txn, s);
|
||||
IntroducerSession session =
|
||||
introducerEngine.onIntroduceeRemoved(txn, i, s);
|
||||
storeSession(txn, storageId, session);
|
||||
} else {
|
||||
db.removeMessage(txn, storageId);
|
||||
|
||||
@@ -19,7 +19,7 @@ public interface TestData {
|
||||
"Ibrahim ibn Sinan",
|
||||
"Muhammad Al-Karaji",
|
||||
"Yang Hui",
|
||||
"René Descartes",
|
||||
"Ren\u00e9 Descartes",
|
||||
"Pierre de Fermat",
|
||||
"Blaise Pascal",
|
||||
"Jacob Bernoulli",
|
||||
@@ -33,13 +33,13 @@ public interface TestData {
|
||||
"George Boole",
|
||||
"John Venn",
|
||||
"Gottlob Frege",
|
||||
"Henri Poincaré",
|
||||
"Henri Poincar\u00e9",
|
||||
"David Hilbert",
|
||||
"Bertrand Russell",
|
||||
"John von Neumann",
|
||||
"Kurt Gödel",
|
||||
"Kurt G\u00f6del",
|
||||
"Alan Turing",
|
||||
"Benoît Mandelbrot",
|
||||
"Beno\u00eet Mandelbrot",
|
||||
"John Nash",
|
||||
getRandomString(MAX_AUTHOR_NAME_LENGTH),
|
||||
getRandomString(MAX_AUTHOR_NAME_LENGTH),
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
|
||||
@@ -918,6 +919,51 @@ public class IntroductionIntegrationTest
|
||||
.getMessageMetadataAsDictionary(group0.getId()).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionAfterReAddingContacts() throws Exception {
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, null, time);
|
||||
|
||||
// 0 and 1 remove and re-add each other
|
||||
contactManager0.removeContact(contactId1From0);
|
||||
contactManager1.removeContact(contactId0From1);
|
||||
contactId1From0 = contactManager0
|
||||
.addContact(author1, author0.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact1From0 = contactManager0.getContact(contactId1From0);
|
||||
contactId0From1 = contactManager1
|
||||
.addContact(author0, author1.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact0From1 = contactManager1.getContact(contactId0From1);
|
||||
|
||||
// Sync initial client versioning updates and transport properties
|
||||
sync0To1(1, true);
|
||||
sync1To0(1, true);
|
||||
sync0To1(2, true);
|
||||
sync1To0(1, true);
|
||||
|
||||
// a new introduction should be possible
|
||||
assertTrue(introductionManager0
|
||||
.canIntroduce(contact1From0, contact2From0));
|
||||
|
||||
// listen to events, so we don't miss new request
|
||||
addListeners(true, true);
|
||||
|
||||
// make new introduction
|
||||
time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, null, time);
|
||||
|
||||
// introduction should sync and not be INVALID or PENDING
|
||||
sync0To1(1, true);
|
||||
|
||||
// assert that new request was received
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
}
|
||||
|
||||
private void testModifiedResponse(StateVisitor visitor)
|
||||
throws Exception {
|
||||
addListeners(true, true);
|
||||
@@ -1099,6 +1145,7 @@ public class IntroductionIntegrationTest
|
||||
protected volatile boolean aborted = false;
|
||||
protected volatile Event latestEvent;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
IntroductionResponse getResponse() {
|
||||
assertTrue(
|
||||
latestEvent instanceof IntroductionResponseReceivedEvent);
|
||||
|
||||
@@ -21,7 +21,6 @@ buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.9'
|
||||
classpath 'de.undercouch:gradle-download-task:3.2.0'
|
||||
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.4.3'
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user