mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 06:39:54 +01:00
Compare commits
68 Commits
beta-2017-
...
beta-2017-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe963edd9d | ||
|
|
96f006068f | ||
|
|
74f1fa5690 | ||
|
|
6b3a1fd6d4 | ||
|
|
bcabcfce8c | ||
|
|
db0a3bf380 | ||
|
|
d5d9436e28 | ||
|
|
9d0dbe9210 | ||
|
|
1f7d1bf515 | ||
|
|
fb85ecf07b | ||
|
|
a931e6b316 | ||
|
|
3aa4644339 | ||
|
|
9a638c804a | ||
|
|
df3254c634 | ||
|
|
ba353b9f2b | ||
|
|
04c4e70dd1 | ||
|
|
d381e25e86 | ||
|
|
0c085f139a | ||
|
|
4123f4a5ce | ||
|
|
7bc269fda4 | ||
|
|
a22931bae6 | ||
|
|
403f886110 | ||
|
|
b7866be38d | ||
|
|
a1b415330e | ||
|
|
58318bb79f | ||
|
|
10bb30e190 | ||
|
|
199a2ffc46 | ||
|
|
f6ad2992f2 | ||
|
|
f039bd1239 | ||
|
|
da22d91ef3 | ||
|
|
cd360ec877 | ||
|
|
8e1ada4cdc | ||
|
|
ac063b4c79 | ||
|
|
10e6163e94 | ||
|
|
ebc3402307 | ||
|
|
d9c63bbcfe | ||
|
|
9c89e83c20 | ||
|
|
adc9bdeb68 | ||
|
|
ff7f0bdc63 | ||
|
|
c5f6980c69 | ||
|
|
2574354997 | ||
|
|
c4e42949cf | ||
|
|
1c5897f1cc | ||
|
|
510f99c7da | ||
|
|
1918346ae8 | ||
|
|
2a59515c72 | ||
|
|
7161152b41 | ||
|
|
b42660edab | ||
|
|
b405bbf98e | ||
|
|
c167938b61 | ||
|
|
24b531e6b2 | ||
|
|
9cffff715a | ||
|
|
804e912e19 | ||
|
|
d67e3900e3 | ||
|
|
e682f31898 | ||
|
|
a9053808b4 | ||
|
|
d9a62a0431 | ||
|
|
15ba73276d | ||
|
|
720dda784e | ||
|
|
0ae55404f5 | ||
|
|
9c41437870 | ||
|
|
da9cde083f | ||
|
|
ce3156c9fe | ||
|
|
be3752bf2f | ||
|
|
ef74db65aa | ||
|
|
867a233b6f | ||
|
|
59af25b2cd | ||
|
|
79c78518fb |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -9,17 +9,18 @@ Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# Eclipse project files
|
||||
#.classpath
|
||||
#.project
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Android Studio
|
||||
.idea/*
|
||||
!.idea/runConfigurations/
|
||||
!.idea/codeStyleSettings.xml
|
||||
.gradle
|
||||
build/
|
||||
*.iml
|
||||
.gitignore
|
||||
src/test/
|
||||
projectFilesBackup/
|
||||
28
.idea/runConfigurations/All_tests.xml
generated
Normal file
28
.idea/runConfigurations/All_tests.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method>
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-api" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_core.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_core.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_j2se.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_j2se.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-j2se" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-j2se" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-j2se" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_briar_android.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_briar_android.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -67,6 +67,7 @@ import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
@@ -164,7 +165,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
bind();
|
||||
} else {
|
||||
// Enable Bluetooth if settings allow
|
||||
if (callback.getSettings().getBoolean("enable", false)) {
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
||||
wasEnabledByUs = true;
|
||||
if (adapter.enable()) LOG.info("Enabling Bluetooth");
|
||||
else LOG.info("Could not enable Bluetooth");
|
||||
|
||||
@@ -79,6 +79,12 @@ import static java.util.logging.Level.WARNING;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -182,19 +188,31 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
String torPath = torFile.getAbsolutePath();
|
||||
String configPath = configFile.getAbsolutePath();
|
||||
String pid = String.valueOf(android.os.Process.myPid());
|
||||
String[] cmd = {torPath, "-f", configPath, OWNER, pid};
|
||||
String[] env = {"HOME=" + torDirectory.getAbsolutePath()};
|
||||
Process torProcess;
|
||||
ProcessBuilder pb =
|
||||
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
|
||||
Map<String, String> env = pb.environment();
|
||||
env.put("HOME", torDirectory.getAbsolutePath());
|
||||
pb.directory(torDirectory);
|
||||
try {
|
||||
torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory);
|
||||
torProcess = pb.start();
|
||||
} catch (SecurityException | IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
// Log the process's standard output until it detaches
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
while (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
||||
if(stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if(stderr.hasNextLine()){
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
stderr.close();
|
||||
}
|
||||
try {
|
||||
// Wait for the process to detach or exit
|
||||
@@ -366,7 +384,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
String portString = callback.getSettings().get("port");
|
||||
String portString = callback.getSettings().get(PREF_TOR_PORT);
|
||||
int port;
|
||||
if (StringUtils.isNullOrEmpty(portString)) port = 0;
|
||||
else port = Integer.parseInt(portString);
|
||||
@@ -389,7 +407,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
// Store the port number
|
||||
final String localPort = String.valueOf(ss.getLocalPort());
|
||||
Settings s = new Settings();
|
||||
s.put("port", localPort);
|
||||
s.put(PREF_TOR_PORT, localPort);
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@@ -666,7 +684,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString())) {
|
||||
LOG.info("Tor settings updated");
|
||||
updateConnectionStatus();
|
||||
}
|
||||
@@ -688,7 +707,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
boolean useMobileData = s.getBoolean("torOverMobile", true);
|
||||
int network = s.getInt(PREF_TOR_NETWORK,
|
||||
PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
@@ -703,7 +723,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (!wifi && !useMobileData) {
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
||||
|
||||
private static final int SEED_LENGTH = 32;
|
||||
|
||||
private final Context appContext;
|
||||
|
||||
@Inject
|
||||
AndroidSecureRandomProvider(Application app) {
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
super.writeToEntropyPool(out);
|
||||
out.writeInt(android.os.Process.myPid());
|
||||
out.writeInt(android.os.Process.myTid());
|
||||
out.writeInt(android.os.Process.myUid());
|
||||
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
|
||||
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
|
||||
ContentResolver contentResolver = appContext.getContentResolver();
|
||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||
if (id != null) out.writeUTF(id);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
WifiManager wm =
|
||||
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
||||
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
|
||||
if (configs != null) {
|
||||
for (WifiConfiguration config : configs)
|
||||
parcel.writeParcelable(config, 0);
|
||||
}
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt != null) {
|
||||
for (BluetoothDevice device : bt.getBondedDevices())
|
||||
parcel.writeParcelable(device, 0);
|
||||
}
|
||||
out.write(parcel.marshall());
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeSeed() {
|
||||
super.writeSeed();
|
||||
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
|
||||
applyOpenSslFix();
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
private void applyOpenSslFix() {
|
||||
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
|
||||
SEED_LENGTH);
|
||||
try {
|
||||
// Seed the OpenSSL PRNG
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_seed", byte[].class)
|
||||
.invoke(null, seed);
|
||||
// Mix the output of the Linux PRNG into the OpenSSL PRNG
|
||||
int bytesRead = (Integer) Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_load_file", String.class, long.class)
|
||||
.invoke(null, "/dev/urandom", 1024);
|
||||
if (bytesRead != 1024) throw new IOException();
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidSeedProvider extends LinuxSeedProvider {
|
||||
|
||||
private final Context appContext;
|
||||
|
||||
@Inject
|
||||
AndroidSeedProvider(Application app) {
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
out.writeInt(android.os.Process.myPid());
|
||||
out.writeInt(android.os.Process.myTid());
|
||||
out.writeInt(android.os.Process.myUid());
|
||||
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
|
||||
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
|
||||
ContentResolver contentResolver = appContext.getContentResolver();
|
||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||
if (id != null) out.writeUTF(id);
|
||||
super.writeToEntropyPool(out);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -16,8 +16,8 @@ public class AndroidSystemModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SeedProvider provideSeedProvider(Application app) {
|
||||
return new AndroidSeedProvider(app);
|
||||
SecureRandomProvider provideSecureRandomProvider(Application app) {
|
||||
return new AndroidSecureRandomProvider(app);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -6,9 +6,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
public interface PasswordStrengthEstimator {
|
||||
|
||||
float NONE = 0;
|
||||
float WEAK = 0.4f;
|
||||
float QUITE_WEAK = 0.6f;
|
||||
float QUITE_STRONG = 0.8f;
|
||||
float WEAK = 0.25f;
|
||||
float QUITE_WEAK = 0.5f;
|
||||
float QUITE_STRONG = 0.75f;
|
||||
float STRONG = 1;
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,4 +9,5 @@ public interface BluetoothConstants {
|
||||
String PROP_ADDRESS = "address";
|
||||
String PROP_UUID = "uuid";
|
||||
|
||||
String PREF_BT_ENABLE = "enable";
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ public interface LanTcpConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.lan");
|
||||
|
||||
String PREF_LAN_IP_PORTS = "ipPorts";
|
||||
}
|
||||
|
||||
@@ -8,4 +8,11 @@ public interface TorConstants {
|
||||
int CONTROL_PORT = 59051;
|
||||
|
||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
||||
|
||||
String PREF_TOR_NETWORK = "network";
|
||||
String PREF_TOR_PORT = "port";
|
||||
|
||||
int PREF_TOR_NETWORK_NEVER = 0;
|
||||
int PREF_TOR_NETWORK_WIFI = 1;
|
||||
int PREF_TOR_NETWORK_ALWAYS = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Wrapper for a platform-specific secure random number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface SecureRandomProvider {
|
||||
|
||||
/**
|
||||
* Returns a {@link Provider} that provides a strong {@link SecureRandom}
|
||||
* implementation, or null if the platform's default implementation should
|
||||
* be used.
|
||||
*/
|
||||
@Nullable
|
||||
Provider getProvider();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Uses a platform-specific source to provide a seed for a pseudo-random
|
||||
* number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface SeedProvider {
|
||||
|
||||
/**
|
||||
* The length of the seed in bytes.
|
||||
*/
|
||||
int SEED_BYTES = 32;
|
||||
|
||||
byte[] getSeed();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
|
||||
/**
|
||||
* An {@link Executor} that delegates its tasks to another {@link Executor}
|
||||
* while limiting the number of tasks that are delegated concurrently. Tasks
|
||||
* are delegated in the order they are submitted to this executor.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public class PoliteExecutor implements Executor {
|
||||
|
||||
private static final Level LOG_LEVEL = FINE;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Runnable> queue = new LinkedList<Runnable>();
|
||||
private final Executor delegate;
|
||||
private final int maxConcurrentTasks;
|
||||
private final Logger log;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private int concurrentTasks = 0;
|
||||
|
||||
/**
|
||||
* @param tag the tag to be used for logging
|
||||
* @param delegate the executor to which tasks will be delegated
|
||||
* @param maxConcurrentTasks the maximum number of tasks that will be
|
||||
* delegated concurrently. If this is set to 1, tasks submitted to this
|
||||
* executor will run in the order they are submitted and will not run
|
||||
* concurrently
|
||||
*/
|
||||
public PoliteExecutor(String tag, Executor delegate,
|
||||
int maxConcurrentTasks) {
|
||||
this.delegate = delegate;
|
||||
this.maxConcurrentTasks = maxConcurrentTasks;
|
||||
log = Logger.getLogger(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable r) {
|
||||
final long submitted = System.currentTimeMillis();
|
||||
Runnable wrapped = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long queued = System.currentTimeMillis() - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
}
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
};
|
||||
synchronized (lock) {
|
||||
if (concurrentTasks < maxConcurrentTasks) {
|
||||
concurrentTasks++;
|
||||
delegate.execute(wrapped);
|
||||
} else {
|
||||
queue.add(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNext() {
|
||||
synchronized (lock) {
|
||||
Runnable next = queue.poll();
|
||||
if (next == null) concurrentTasks--;
|
||||
else delegate.execute(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TimeLoggingExecutor extends ThreadPoolExecutor {
|
||||
|
||||
private static final Level LOG_LEVEL = FINE;
|
||||
|
||||
private final Logger log;
|
||||
|
||||
public TimeLoggingExecutor(String tag, int corePoolSize, int maxPoolSize,
|
||||
long keepAliveTime, TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue,
|
||||
RejectedExecutionHandler handler) {
|
||||
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
|
||||
handler);
|
||||
log = Logger.getLogger(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable r) {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
final long submitted = System.currentTimeMillis();
|
||||
super.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long started = System.currentTimeMillis();
|
||||
long queued = started - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
r.run();
|
||||
long executing = System.currentTimeMillis() - started;
|
||||
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.execute(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
|
||||
/**
|
||||
* A {@link SecureRandom} implementation that combines the outputs of two or
|
||||
* more other implementations using XOR.
|
||||
*/
|
||||
class CombinedSecureRandom extends SecureRandom {
|
||||
|
||||
private static final Provider PROVIDER = new CombinedProvider();
|
||||
|
||||
CombinedSecureRandom(SecureRandom... randoms) {
|
||||
super(new CombinedSecureRandomSpi(randoms), PROVIDER);
|
||||
}
|
||||
|
||||
private static class CombinedSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final SecureRandom[] randoms;
|
||||
|
||||
private CombinedSecureRandomSpi(SecureRandom... randoms) {
|
||||
if (randoms.length < 2) throw new IllegalArgumentException();
|
||||
this.randoms = randoms;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int numBytes) {
|
||||
byte[] combined = new byte[numBytes];
|
||||
for (SecureRandom random : randoms) {
|
||||
byte[] b = random.generateSeed(numBytes);
|
||||
int length = Math.min(numBytes, b.length);
|
||||
for (int i = 0; i < length; i++)
|
||||
combined[i] = (byte) (combined[i] ^ b[i]);
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
byte[] temp = new byte[b.length];
|
||||
for (SecureRandom random : randoms) {
|
||||
random.nextBytes(temp);
|
||||
for (int i = 0; i < b.length; i++)
|
||||
b[i] = (byte) (b[i] ^ temp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
for (SecureRandom random : randoms) random.setSeed(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CombinedProvider extends Provider {
|
||||
|
||||
private CombinedProvider() {
|
||||
super("Combined", 1.0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -29,7 +29,10 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -101,16 +104,26 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private final MessageEncrypter messageEncrypter;
|
||||
|
||||
@Inject
|
||||
CryptoComponentImpl(SeedProvider seedProvider) {
|
||||
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
|
||||
SecureRandom platformSecureRandom = new SecureRandom();
|
||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String provider = platformSecureRandom.getProvider().getName();
|
||||
String algorithm = platformSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + provider + " " + algorithm);
|
||||
SecureRandom defaultSecureRandom = new SecureRandom();
|
||||
String name = defaultSecureRandom.getProvider().getName();
|
||||
String algorithm = defaultSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + name + " " + algorithm);
|
||||
}
|
||||
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
|
||||
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
|
||||
Provider provider = secureRandomProvider.getProvider();
|
||||
if (provider == null) {
|
||||
LOG.info("Using default");
|
||||
} else {
|
||||
installSecureRandomProvider(provider);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
SecureRandom installedSecureRandom = new SecureRandom();
|
||||
String name = installedSecureRandom.getProvider().getName();
|
||||
String algorithm = installedSecureRandom.getAlgorithm();
|
||||
LOG.info("Installed SecureRandom: " + name + " " + algorithm);
|
||||
}
|
||||
}
|
||||
secureRandom = new SecureRandom();
|
||||
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
|
||||
PARAMETERS, secureRandom);
|
||||
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
||||
@@ -124,6 +137,31 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
private void installSecureRandomProvider(Provider provider) {
|
||||
Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
|
||||
if (providers == null || providers.length == 0
|
||||
|| !provider.getClass().equals(providers[0].getClass())) {
|
||||
Security.insertProviderAt(provider, 1);
|
||||
}
|
||||
// Check the new provider is the default when no algorithm is specified
|
||||
SecureRandom random = new SecureRandom();
|
||||
if (!provider.getClass().equals(random.getProvider().getClass())) {
|
||||
throw new SecurityException("Wrong SecureRandom provider: "
|
||||
+ random.getProvider().getClass());
|
||||
}
|
||||
// Check the new provider is the default when SHA1PRNG is specified
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
if (!provider.getClass().equals(random.getProvider().getClass())) {
|
||||
throw new SecurityException("Wrong SHA1PRNG provider: "
|
||||
+ random.getProvider().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -133,7 +171,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
@Override
|
||||
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
|
||||
return new PseudoRandomImpl(seed1, seed2);
|
||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
||||
ByteUtils.writeUint32(seed1, seed, 0);
|
||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
||||
return new PseudoRandomImpl(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,7 +337,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||
return deriveMasterSecret(deriveSharedSecret(
|
||||
theirPublicKey,ourKeyPair, alice));
|
||||
theirPublicKey, ourKeyPair, alice));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -607,7 +648,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
private long sampleRunningTime(int iterations) {
|
||||
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
|
||||
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
|
||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||
int keyLengthInBits = SecretKey.LENGTH * 8;
|
||||
long start = System.nanoTime();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -31,14 +32,17 @@ public class CryptoModule {
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
Executor cryptoExecutor;
|
||||
ExecutorService cryptoExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of executor threads.
|
||||
* <p>
|
||||
* The number of available processors can change during the lifetime of the
|
||||
* JVM, so this is just a reasonable guess.
|
||||
*/
|
||||
private static final int MAX_EXECUTOR_THREADS =
|
||||
Runtime.getRuntime().availableProcessors();
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private final ExecutorService cryptoExecutor;
|
||||
|
||||
@@ -49,8 +53,8 @@ public class CryptoModule {
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
|
||||
60, SECONDS, queue, policy);
|
||||
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
||||
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -60,8 +64,9 @@ public class CryptoModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
|
||||
return new CryptoComponentImpl(seedProvider);
|
||||
CryptoComponent provideCryptoComponent(
|
||||
SecureRandomProvider secureRandomProvider) {
|
||||
return new CryptoComponentImpl(secureRandomProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -84,11 +89,18 @@ public class CryptoModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@CryptoExecutor
|
||||
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
|
||||
ExecutorService getCryptoExecutorService(
|
||||
LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CryptoExecutor
|
||||
Executor getCryptoExecutor() {
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SecureRandom getSecureRandom(CryptoComponent crypto) {
|
||||
return crypto.getSecureRandom();
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A message digest that prevents length extension attacks - see Ferguson and
|
||||
* Schneier, <i>Practical Cryptography</i>, chapter 6.
|
||||
* <p>
|
||||
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
|
||||
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
|
||||
* min(k, n/2) where k is the security level of h and n is the size of the hash
|
||||
* result."
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class DoubleDigest implements Digest {
|
||||
|
||||
private final Digest delegate;
|
||||
|
||||
DoubleDigest(Digest delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
private byte[] digest() {
|
||||
byte[] digest = new byte[delegate.getDigestSize()];
|
||||
delegate.doFinal(digest, 0); // h(m)
|
||||
delegate.update(digest, 0, digest.length);
|
||||
delegate.doFinal(digest, 0); // h(h(m))
|
||||
return digest;
|
||||
}
|
||||
|
||||
public int digest(byte[] buf, int offset, int len) {
|
||||
byte[] digest = digest();
|
||||
len = Math.min(len, digest.length);
|
||||
System.arraycopy(digest, 0, buf, offset, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDigestSize() {
|
||||
return delegate.getDigestSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return "Double " + delegate.getAlgorithmName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte input) {
|
||||
delegate.update(input);
|
||||
}
|
||||
|
||||
public void update(byte[] input) {
|
||||
delegate.update(input, 0, input.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] input, int offset, int len) {
|
||||
delegate.update(input, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int doFinal(byte[] out, int outOff) {
|
||||
return digest(out, outOff, delegate.getDigestSize());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Implements the Fortuna pseudo-random number generator, as described in
|
||||
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class FortunaGenerator {
|
||||
|
||||
private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
|
||||
private static final int KEY_BYTES = 32;
|
||||
private static final int BLOCK_BYTES = 16;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
|
||||
private final BlockCipher cipher = new AESLightEngine();
|
||||
private final byte[] key = new byte[KEY_BYTES];
|
||||
private final byte[] counter = new byte[BLOCK_BYTES];
|
||||
private final byte[] buffer = new byte[BLOCK_BYTES];
|
||||
private final byte[] newKey = new byte[KEY_BYTES];
|
||||
|
||||
FortunaGenerator(byte[] seed) {
|
||||
reseed(seed);
|
||||
}
|
||||
|
||||
void reseed(byte[] seed) {
|
||||
lock.lock();
|
||||
try {
|
||||
digest.update(key);
|
||||
digest.update(seed);
|
||||
digest.digest(key, 0, KEY_BYTES);
|
||||
incrementCounter();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
void incrementCounter() {
|
||||
lock.lock();
|
||||
try {
|
||||
counter[0]++;
|
||||
for (int i = 0; counter[i] == 0; i++) {
|
||||
if (i + 1 == BLOCK_BYTES)
|
||||
throw new RuntimeException("Counter exhausted");
|
||||
counter[i + 1]++;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
byte[] getCounter() {
|
||||
lock.lock();
|
||||
try {
|
||||
return counter;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int nextBytes(byte[] dest, int off, int len) {
|
||||
lock.lock();
|
||||
try {
|
||||
// Don't write more than the maximum number of bytes in one request
|
||||
if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
|
||||
cipher.init(true, new KeyParameter(key));
|
||||
// Generate full blocks directly into the output buffer
|
||||
int fullBlocks = len / BLOCK_BYTES;
|
||||
for (int i = 0; i < fullBlocks; i++) {
|
||||
cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
|
||||
incrementCounter();
|
||||
}
|
||||
// Generate a partial block if needed
|
||||
int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
|
||||
if (remaining >= BLOCK_BYTES) throw new AssertionError();
|
||||
if (remaining > 0) {
|
||||
cipher.processBlock(counter, 0, buffer, 0);
|
||||
incrementCounter();
|
||||
// Copy the partial block to the output buffer and erase our copy
|
||||
System.arraycopy(buffer, 0, dest, off + done, remaining);
|
||||
for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
|
||||
}
|
||||
// Generate a new key
|
||||
for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
|
||||
cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
|
||||
incrementCounter();
|
||||
}
|
||||
System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
|
||||
for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
|
||||
// Return the number of bytes written
|
||||
return len;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link java.security.SecureRandom SecureRandom} implementation based on a
|
||||
* {@link FortunaGenerator}.
|
||||
*/
|
||||
class FortunaSecureRandom extends SecureRandom {
|
||||
|
||||
// Package access for testing
|
||||
static final byte[] SELF_TEST_VECTOR_1 =
|
||||
StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
|
||||
static final byte[] SELF_TEST_VECTOR_2 =
|
||||
StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
|
||||
static final byte[] SELF_TEST_VECTOR_3 =
|
||||
StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
|
||||
|
||||
private static final Provider PROVIDER = new FortunaProvider();
|
||||
|
||||
FortunaSecureRandom(byte[] seed) {
|
||||
super(new FortunaSecureRandomSpi(seed), PROVIDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
|
||||
* methods are passed through to the generator in the expected way.
|
||||
*/
|
||||
static boolean selfTest() {
|
||||
byte[] seed = new byte[32];
|
||||
SecureRandom r = new FortunaSecureRandom(seed);
|
||||
byte[] output = new byte[16];
|
||||
r.nextBytes(output);
|
||||
if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
|
||||
r.nextBytes(output);
|
||||
if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
|
||||
r.setSeed(seed);
|
||||
r.nextBytes(output);
|
||||
return Arrays.equals(SELF_TEST_VECTOR_3, output);
|
||||
}
|
||||
|
||||
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final FortunaGenerator generator;
|
||||
|
||||
private FortunaSecureRandomSpi(byte[] seed) {
|
||||
generator = new FortunaGenerator(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int numBytes) {
|
||||
byte[] b = new byte[numBytes];
|
||||
engineNextBytes(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
int offset = 0;
|
||||
while (offset < b.length)
|
||||
offset += generator.nextBytes(b, offset, b.length - offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
generator.reseed(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FortunaProvider extends Provider {
|
||||
|
||||
private FortunaProvider() {
|
||||
super("Fortuna", 1.0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,14 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
||||
|
||||
private static final int LOWER = 26;
|
||||
private static final int UPPER = 26;
|
||||
private static final int DIGIT = 10;
|
||||
private static final int OTHER = 10;
|
||||
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
|
||||
DIGIT + OTHER, 10));
|
||||
// The minimum number of unique characters in a strong password
|
||||
private static final int STRONG_UNIQUE_CHARS = 12;
|
||||
|
||||
@Override
|
||||
public float estimateStrength(String password) {
|
||||
HashSet<Character> unique = new HashSet<Character>();
|
||||
int length = password.length();
|
||||
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
|
||||
boolean lower = false, upper = false, digit = false, other = false;
|
||||
for (char c : unique) {
|
||||
if (Character.isLowerCase(c)) lower = true;
|
||||
else if (Character.isUpperCase(c)) upper = true;
|
||||
else if (Character.isDigit(c)) digit = true;
|
||||
else other = true;
|
||||
}
|
||||
int alphabetSize = 0;
|
||||
if (lower) alphabetSize += LOWER;
|
||||
if (upper) alphabetSize += UPPER;
|
||||
if (digit) alphabetSize += DIGIT;
|
||||
if (other) alphabetSize += OTHER;
|
||||
double score = Math.log(Math.pow(alphabetSize, unique.size()));
|
||||
return Math.min(1, (float) (score / STRONG));
|
||||
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,34 @@ package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
|
||||
private final FortunaGenerator generator;
|
||||
private final Salsa20Engine cipher = new Salsa20Engine();
|
||||
|
||||
PseudoRandomImpl(int seed1, int seed2) {
|
||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
||||
ByteUtils.writeUint32(seed1, seed, 0);
|
||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
||||
generator = new FortunaGenerator(seed);
|
||||
PseudoRandomImpl(byte[] seed) {
|
||||
// Hash the seed to produce a 32-byte key
|
||||
byte[] key = new byte[32];
|
||||
Digest digest = new Blake2sDigest();
|
||||
digest.update(seed, 0, seed.length);
|
||||
digest.doFinal(key, 0);
|
||||
// Initialise the stream cipher with an all-zero nonce
|
||||
byte[] nonce = new byte[8];
|
||||
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextBytes(int length) {
|
||||
byte[] b = new byte[length];
|
||||
int offset = 0;
|
||||
while (offset < length) offset += generator.nextBytes(b, offset, length);
|
||||
return b;
|
||||
byte[] in = new byte[length], out = new byte[length];
|
||||
cipher.processBytes(in, 0, length, out, 0);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
@Override
|
||||
public void writeFrame(byte[] payload, int payloadLength,
|
||||
int paddingLength, boolean finalFrame) throws IOException {
|
||||
if (payloadLength < 0 || paddingLength < 0)
|
||||
throw new IllegalArgumentException();
|
||||
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
// Don't allow the frame counter to wrap
|
||||
|
||||
@@ -67,6 +67,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -130,8 +131,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
// Don't allow reentrant locking
|
||||
if (lock.getReadHoldCount() > 0) throw new IllegalStateException();
|
||||
if (lock.getWriteHoldCount() > 0) throw new IllegalStateException();
|
||||
long start = System.currentTimeMillis();
|
||||
if (readOnly) lock.readLock().lock();
|
||||
else lock.writeLock().lock();
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
if (readOnly) LOG.fine("Waited " + duration + " ms for read lock");
|
||||
else LOG.fine("Waited " + duration + " ms for write lock");
|
||||
}
|
||||
try {
|
||||
return new Transaction(db.startTransaction(), readOnly);
|
||||
} catch (DbException e) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
|
||||
@@ -36,8 +37,8 @@ public class DatabaseExecutorModule {
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Use a single thread and keep it in the pool for 60 secs
|
||||
databaseExecutor = new ThreadPoolExecutor(0, 1, 60, SECONDS, queue,
|
||||
policy);
|
||||
databaseExecutor = new TimeLoggingExecutor("DatabaseExecutor", 0, 1,
|
||||
60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -34,6 +34,7 @@ import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
|
||||
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||
|
||||
@@ -43,7 +44,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||
|
||||
private static final int MAX_ADDRESSES = 5;
|
||||
private static final int MAX_ADDRESSES = 4;
|
||||
private static final String PROP_IP_PORTS = "ipPorts";
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
@@ -82,19 +83,19 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
|
||||
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
|
||||
String[] split = ipPorts.split(SEPARATOR);
|
||||
List<InetSocketAddress> remotes = new ArrayList<InetSocketAddress>();
|
||||
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
|
||||
for (String ipPort : split) {
|
||||
InetSocketAddress a = parseSocketAddress(ipPort);
|
||||
if (a != null) remotes.add(a);
|
||||
if (a != null) addresses.add(a);
|
||||
}
|
||||
return remotes;
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLocalSocketAddress(InetSocketAddress a) {
|
||||
String ipPort = getIpPortString(a);
|
||||
// Get the list of recently used addresses
|
||||
String setting = callback.getSettings().get(PROP_IP_PORTS);
|
||||
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
|
||||
List<String> recent = new ArrayList<String>();
|
||||
if (!StringUtils.isNullOrEmpty(setting))
|
||||
Collections.addAll(recent, setting.split(SEPARATOR));
|
||||
@@ -120,7 +121,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
// Save the setting
|
||||
Settings settings = new Settings();
|
||||
settings.put(PROP_IP_PORTS, setting);
|
||||
settings.put(PREF_LAN_IP_PORTS, setting);
|
||||
callback.mergeSettings(settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,28 @@ import org.briarproject.bramble.util.IoUtils;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Arrays;
|
||||
|
||||
class SocksSocket extends Socket {
|
||||
|
||||
private static final String[] ERRORS = {
|
||||
"Succeeded",
|
||||
"General SOCKS server failure",
|
||||
"Connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported"
|
||||
};
|
||||
|
||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||
|
||||
private final SocketAddress proxy;
|
||||
private final int connectToProxyTimeout;
|
||||
|
||||
@@ -28,6 +44,11 @@ class SocksSocket extends Socket {
|
||||
if (!(endpoint instanceof InetSocketAddress))
|
||||
throw new IllegalArgumentException();
|
||||
InetSocketAddress inet = (InetSocketAddress) endpoint;
|
||||
InetAddress address = inet.getAddress();
|
||||
if (address != null
|
||||
&& !Arrays.equals(address.getAddress(), UNSPECIFIED_ADDRESS)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
String host = inet.getHostName();
|
||||
if (host.length() > 255) throw new IllegalArgumentException();
|
||||
int port = inet.getPort();
|
||||
@@ -93,13 +114,16 @@ class SocksSocket extends Socket {
|
||||
private void receiveConnectResponse(InputStream in) throws IOException {
|
||||
byte[] connectResponse = new byte[4];
|
||||
IoUtils.read(in, connectResponse);
|
||||
byte version = connectResponse[0];
|
||||
byte reply = connectResponse[1];
|
||||
byte addressType = connectResponse[3];
|
||||
int version = connectResponse[0] & 0xFF;
|
||||
int reply = connectResponse[1] & 0xFF;
|
||||
int addressType = connectResponse[3] & 0xFF;
|
||||
if (version != 5)
|
||||
throw new IOException("Unsupported SOCKS version: " + version);
|
||||
if (reply != 0)
|
||||
throw new IOException("Connection failed: " + reply);
|
||||
if (reply != 0) {
|
||||
if (reply < ERRORS.length)
|
||||
throw new IOException("Connection failed: " + ERRORS[reply]);
|
||||
else throw new IOException("Connection failed: " + reply);
|
||||
}
|
||||
if (addressType == 1) IoUtils.read(in, new byte[4]); // IPv4
|
||||
else if (addressType == 4) IoUtils.read(in, new byte[16]); // IPv6
|
||||
else throw new IOException("Unsupported address type: " + addressType);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
@@ -29,6 +31,16 @@ public class SyncModule {
|
||||
ValidationManager validationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of validation tasks to delegate to the crypto
|
||||
* executor concurrently.
|
||||
* <p>
|
||||
* The number of available processors can change during the lifetime of the
|
||||
* JVM, so this is just a reasonable guess.
|
||||
*/
|
||||
private static final int MAX_CONCURRENT_VALIDATION_TASKS =
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
@Provides
|
||||
GroupFactory provideGroupFactory(CryptoComponent crypto) {
|
||||
return new GroupFactoryImpl(crypto);
|
||||
@@ -62,10 +74,20 @@ public class SyncModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ValidationManager getValidationManager(LifecycleManager lifecycleManager,
|
||||
EventBus eventBus, ValidationManagerImpl validationManager) {
|
||||
ValidationManager provideValidationManager(
|
||||
LifecycleManager lifecycleManager, EventBus eventBus,
|
||||
ValidationManagerImpl validationManager) {
|
||||
lifecycleManager.registerService(validationManager);
|
||||
eventBus.addListener(validationManager);
|
||||
return validationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ValidationExecutor
|
||||
Executor provideValidationExecutor(
|
||||
@CryptoExecutor Executor cryptoExecutor) {
|
||||
return new PoliteExecutor("ValidationExecutor", cryptoExecutor,
|
||||
MAX_CONCURRENT_VALIDATION_TASKS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotation for injecting the executor for validation tasks. Also used for
|
||||
* annotating methods that should run on the validation executor.
|
||||
* <p>
|
||||
* The contract of this executor is that tasks may be run concurrently, and
|
||||
* submitting a task will never block. Tasks must not run indefinitely. Tasks
|
||||
* submitted during shutdown are discarded.
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({FIELD, METHOD, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
@interface ValidationExecutor {
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -50,8 +49,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
Logger.getLogger(ValidationManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
private final Executor cryptoExecutor;
|
||||
private final Executor dbExecutor, validationExecutor;
|
||||
private final MessageFactory messageFactory;
|
||||
private final Map<ClientId, MessageValidator> validators;
|
||||
private final Map<ClientId, IncomingMessageHook> hooks;
|
||||
@@ -60,11 +58,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@Inject
|
||||
ValidationManagerImpl(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
@ValidationExecutor Executor validationExecutor,
|
||||
MessageFactory messageFactory) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.validationExecutor = validationExecutor;
|
||||
this.messageFactory = messageFactory;
|
||||
validators = new ConcurrentHashMap<ClientId, MessageValidator>();
|
||||
hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
|
||||
@@ -104,6 +102,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void validateOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
|
||||
@@ -130,6 +129,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void validateNextMessage(Queue<MessageId> unvalidated) {
|
||||
try {
|
||||
Message m;
|
||||
@@ -167,6 +167,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void deliverOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
@@ -194,6 +195,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void deliverNextPendingMessage(Queue<MessageId> pending) {
|
||||
try {
|
||||
boolean anyInvalid = false, allDelivered = true;
|
||||
@@ -220,8 +222,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
Message m = messageFactory.createMessage(id, raw);
|
||||
Group g = db.getGroup(txn, m.getGroupId());
|
||||
ClientId c = g.getClientId();
|
||||
Metadata meta = db.getMessageMetadataForValidator(txn,
|
||||
id);
|
||||
Metadata meta =
|
||||
db.getMessageMetadataForValidator(txn, id);
|
||||
DeliveryResult result = deliverMessage(txn, m, c, meta);
|
||||
if (result.valid) {
|
||||
pending.addAll(getPendingDependents(txn, id));
|
||||
@@ -240,8 +242,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
if (invalidate != null) invalidateNextMessageAsync(invalidate);
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
if (toShare != null) shareNextMessageAsync(toShare);
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (NoSuchMessageException e) {
|
||||
LOG.info("Message removed before delivery");
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
@@ -249,13 +251,12 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
LOG.info("Group removed before delivery");
|
||||
deliverNextPendingMessageAsync(pending);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMessageAsync(final Message m, final Group g) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
validationExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
validateMessage(m, g);
|
||||
@@ -263,10 +264,12 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@ValidationExecutor
|
||||
private void validateMessage(Message m, Group g) {
|
||||
MessageValidator v = validators.get(g.getClientId());
|
||||
if (v == null) {
|
||||
LOG.warning("No validator");
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("No validator for " + g.getClientId().getString());
|
||||
} else {
|
||||
try {
|
||||
MessageContext context = v.validateMessage(m, g);
|
||||
@@ -291,6 +294,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void storeMessageContext(Message m, ClientId c,
|
||||
MessageContext context) {
|
||||
try {
|
||||
@@ -353,6 +357,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
||||
ClientId c, Metadata meta) throws DbException {
|
||||
// Deliver the message to the client if it's registered a hook
|
||||
@@ -362,10 +367,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
try {
|
||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||
} catch (InvalidMessageException e) {
|
||||
// message is invalid, mark it as such and delete it
|
||||
db.setMessageState(txn, m.getId(), INVALID);
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
db.deleteMessage(txn, m.getId());
|
||||
invalidateMessage(txn, m.getId());
|
||||
return new DeliveryResult(false, false);
|
||||
}
|
||||
}
|
||||
@@ -373,6 +375,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return new DeliveryResult(true, shareMsg);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
@@ -392,6 +395,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void shareOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> toShare = new LinkedList<MessageId>();
|
||||
@@ -424,6 +428,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void shareNextMessage(Queue<MessageId> toShare) {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -457,6 +462,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void invalidateNextMessage(Queue<MessageId> invalidate) {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -479,6 +485,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void invalidateMessage(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
db.setMessageState(txn, m, INVALID);
|
||||
@@ -486,6 +493,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
db.deleteMessageMetadata(txn, m);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
|
||||
MessageId m) throws DbException {
|
||||
Queue<MessageId> invalidate = new LinkedList<MessageId>();
|
||||
@@ -515,6 +523,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void loadGroupAndValidate(final Message m) {
|
||||
try {
|
||||
Group g;
|
||||
@@ -534,6 +543,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
|
||||
private static class DeliveryResult {
|
||||
|
||||
private final boolean valid, share;
|
||||
|
||||
private DeliveryResult(boolean valid, boolean share) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
|
||||
|
||||
// Contribute whatever slightly unpredictable info we have to the pool
|
||||
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
out.writeLong(System.currentTimeMillis());
|
||||
out.writeLong(System.nanoTime());
|
||||
out.writeLong(Runtime.getRuntime().freeMemory());
|
||||
List<NetworkInterface> ifaces =
|
||||
Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface i : ifaces) {
|
||||
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
|
||||
for (InetAddress a : addrs) out.write(a.getAddress());
|
||||
byte[] hardware = i.getHardwareAddress();
|
||||
if (hardware != null) out.write(hardware);
|
||||
}
|
||||
for (Entry<String, String> e : System.getenv().entrySet()) {
|
||||
out.writeUTF(e.getKey());
|
||||
out.writeUTF(e.getValue());
|
||||
}
|
||||
Properties properties = System.getProperties();
|
||||
for (String key : properties.stringPropertyNames())
|
||||
out.writeUTF(properties.getProperty(key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.Provider;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LinuxSecureRandomProvider.class.getName());
|
||||
|
||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||
|
||||
private final AtomicBoolean seeded = new AtomicBoolean(false);
|
||||
private final File outputDevice;
|
||||
|
||||
LinuxSecureRandomProvider() {
|
||||
this(RANDOM_DEVICE);
|
||||
}
|
||||
|
||||
LinuxSecureRandomProvider(File outputDevice) {
|
||||
this.outputDevice = outputDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider getProvider() {
|
||||
if (!seeded.getAndSet(true)) writeSeed();
|
||||
return new LinuxProvider();
|
||||
}
|
||||
|
||||
protected void writeSeed() {
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new FileOutputStream(outputDevice));
|
||||
writeToEntropyPool(out);
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// On some devices /dev/urandom isn't writable - this isn't fatal
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
private static class LinuxProvider extends Provider {
|
||||
|
||||
private LinuxProvider() {
|
||||
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom");
|
||||
// Although /dev/urandom is not a SHA-1 PRNG, some callers
|
||||
// explicitly request a SHA1PRNG SecureRandom and we need to
|
||||
// prevent them from getting the default implementation whose
|
||||
// output may have low entropy.
|
||||
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName());
|
||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class LinuxSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LinuxSecureRandomSpi.class.getName());
|
||||
|
||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||
|
||||
private final File inputDevice, outputDevice;
|
||||
|
||||
public LinuxSecureRandomSpi() {
|
||||
this(RANDOM_DEVICE, RANDOM_DEVICE);
|
||||
}
|
||||
|
||||
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
|
||||
this.inputDevice = inputDevice;
|
||||
this.outputDevice = outputDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new FileOutputStream(outputDevice));
|
||||
out.write(seed);
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// On some devices /dev/urandom isn't writable - this isn't fatal
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] bytes) {
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(
|
||||
new FileInputStream(inputDevice));
|
||||
in.readFully(bytes);
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int len) {
|
||||
byte[] seed = new byte[len];
|
||||
engineNextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class LinuxSeedProvider implements SeedProvider {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LinuxSeedProvider.class.getName());
|
||||
|
||||
private final String outputFile, inputFile;
|
||||
|
||||
LinuxSeedProvider() {
|
||||
this("/dev/urandom", "/dev/urandom");
|
||||
}
|
||||
|
||||
LinuxSeedProvider(String outputFile, String inputFile) {
|
||||
this.outputFile = outputFile;
|
||||
this.inputFile = inputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSeed() {
|
||||
byte[] seed = new byte[SEED_BYTES];
|
||||
// Contribute whatever slightly unpredictable info we have to the pool
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
new FileOutputStream(outputFile));
|
||||
writeToEntropyPool(out);
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// On some devices /dev/urandom isn't writable - this isn't fatal
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
// Read the seed from the pool
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(
|
||||
new FileInputStream(inputFile));
|
||||
in.readFully(seed);
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
out.writeLong(System.currentTimeMillis());
|
||||
out.writeLong(System.nanoTime());
|
||||
List<NetworkInterface> ifaces =
|
||||
Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface i : ifaces) {
|
||||
List<InetAddress> addrs = Collections.list(i.getInetAddresses());
|
||||
for (InetAddress a : addrs) out.write(a.getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PoliteExecutorTest extends BrambleTestCase {
|
||||
|
||||
private static final String TAG = "Test";
|
||||
private static final int TASKS = 10;
|
||||
|
||||
@Test
|
||||
public void testTasksAreDelegatedInOrderOfSubmission() throws Exception {
|
||||
// Delegate to a single-threaded executor
|
||||
Executor delegate = Executors.newSingleThreadExecutor();
|
||||
// Allow all the tasks to be delegated straight away
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
latch.await();
|
||||
// The tasks should have run in the order they were submitted
|
||||
assertEquals(ascendingOrder(), list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueuedTasksAreDelegatedInOrderOfSubmission()
|
||||
throws Exception {
|
||||
// Delegate to a single-threaded executor
|
||||
Executor delegate = Executors.newSingleThreadExecutor();
|
||||
// Allow two tasks to be delegated at a time
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
latch.await();
|
||||
// The tasks should have run in the order they were submitted
|
||||
assertEquals(ascendingOrder(), list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTasksRunInParallelOnDelegate() throws Exception {
|
||||
// Delegate to a multi-threaded executor
|
||||
Executor delegate = Executors.newCachedThreadPool();
|
||||
// Allow all the tasks to be delegated straight away
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch[] latches = new CountDownLatch[TASKS];
|
||||
for (int i = 0; i < TASKS; i++) latches[i] = new CountDownLatch(1);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Each task waits for the next task, if any, to finish
|
||||
if (result < TASKS - 1) latches[result + 1].await();
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
latches[result].countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
for (int i = 0; i < TASKS; i++) latches[i].await();
|
||||
// The tasks should have finished in reverse order
|
||||
assertEquals(descendingOrder(), list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTasksDoNotRunInParallelOnDelegate() throws Exception {
|
||||
// Delegate to a multi-threaded executor
|
||||
Executor delegate = Executors.newCachedThreadPool();
|
||||
// Allow one task to be delegated at a time
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Each task runs faster than the previous task
|
||||
Thread.sleep(TASKS - result);
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
latch.await();
|
||||
// The tasks should have finished in the order they were submitted
|
||||
assertEquals(ascendingOrder(), list);
|
||||
}
|
||||
|
||||
private List<Integer> ascendingOrder() {
|
||||
Integer[] array = new Integer[TASKS];
|
||||
for (int i = 0; i < TASKS; i++) array[i] = i;
|
||||
return Arrays.asList(array);
|
||||
}
|
||||
|
||||
private List<Integer> descendingOrder() {
|
||||
Integer[] array = new Integer[TASKS];
|
||||
for (int i = 0; i < TASKS; i++) array[i] = TASKS - 1 - i;
|
||||
return Arrays.asList(array);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
byte[] seed = new byte[32];
|
||||
new SecureRandom().nextBytes(seed);
|
||||
// Montgomery ladder multiplier
|
||||
SecureRandom random = new FortunaSecureRandom(seed);
|
||||
SecureRandom random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters montgomeryGeneratorParams =
|
||||
new ECKeyGenerationParameters(PARAMETERS, random);
|
||||
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
||||
@@ -63,7 +63,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
ECPublicKeyParameters montgomeryPublic2 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
|
||||
// Default multiplier
|
||||
random = new FortunaSecureRandom(seed);
|
||||
random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters defaultGeneratorParams =
|
||||
new ECKeyGenerationParameters(defaultParameters, random);
|
||||
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class FortunaGeneratorTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testCounterInitialisedToOne() {
|
||||
FortunaGenerator f = new FortunaGenerator(new byte[32]);
|
||||
// The counter is little-endian
|
||||
byte[] expected = new byte[16];
|
||||
expected[0] = 1;
|
||||
assertArrayEquals(expected, f.getCounter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementCounter() {
|
||||
FortunaGenerator f = new FortunaGenerator(new byte[32]);
|
||||
// Increment the counter until it reaches 255
|
||||
for (int i = 1; i < 255; i++) f.incrementCounter();
|
||||
byte[] expected = new byte[16];
|
||||
expected[0] = (byte) 255;
|
||||
assertArrayEquals(expected, f.getCounter());
|
||||
// Increment the counter again - it should carry into the next byte
|
||||
f.incrementCounter();
|
||||
expected[0] = 0;
|
||||
expected[1] = 1;
|
||||
assertArrayEquals(expected, f.getCounter());
|
||||
// Increment the counter until it carries into the next byte
|
||||
for (int i = 256; i < 65536; i++) f.incrementCounter();
|
||||
expected[0] = 0;
|
||||
expected[1] = 0;
|
||||
expected[2] = 1;
|
||||
assertArrayEquals(expected, f.getCounter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextBytes() {
|
||||
// Generate several outputs with the same seed - they should all match
|
||||
byte[] seed = new byte[32];
|
||||
byte[] out1 = new byte[48];
|
||||
new FortunaGenerator(seed).nextBytes(out1, 0, 48);
|
||||
// One byte longer than a block, with an offset of one
|
||||
byte[] out2 = new byte[49];
|
||||
new FortunaGenerator(seed).nextBytes(out2, 1, 48);
|
||||
for (int i = 0; i < 48; i++) assertEquals(out1[i], out2[i + 1]);
|
||||
// One byte shorter than a block
|
||||
byte[] out3 = new byte[47];
|
||||
new FortunaGenerator(seed).nextBytes(out3, 0, 47);
|
||||
for (int i = 0; i < 47; i++) assertEquals(out1[i], out3[i]);
|
||||
// Less than a block, with an offset greater than a block
|
||||
byte[] out4 = new byte[32];
|
||||
new FortunaGenerator(seed).nextBytes(out4, 17, 15);
|
||||
for (int i = 0; i < 15; i++) assertEquals(out1[i], out4[i + 17]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRekeying() {
|
||||
byte[] seed = new byte[32];
|
||||
FortunaGenerator f = new FortunaGenerator(seed);
|
||||
// Generate three blocks of output
|
||||
byte[] out1 = new byte[48];
|
||||
f.nextBytes(out1, 0, 48);
|
||||
// Create another generator with the same seed and generate one block
|
||||
f = new FortunaGenerator(seed);
|
||||
byte[] out2 = new byte[16];
|
||||
f.nextBytes(out2, 0, 16);
|
||||
// The generator should have rekeyed with the 2nd and 3rd blocks
|
||||
byte[] expectedKey = new byte[32];
|
||||
System.arraycopy(out1, 16, expectedKey, 0, 32);
|
||||
// The generator's counter should have been incremented 3 times
|
||||
byte[] expectedCounter = new byte[16];
|
||||
expectedCounter[0] = 4;
|
||||
// The next expected output block is the counter encrypted with the key
|
||||
byte[] expectedOutput = new byte[16];
|
||||
BlockCipher c = new AESLightEngine();
|
||||
c.init(true, new KeyParameter(expectedKey));
|
||||
c.processBlock(expectedCounter, 0, expectedOutput, 0);
|
||||
// Check that the generator produces the expected output block
|
||||
byte[] out3 = new byte[16];
|
||||
f.nextBytes(out3, 0, 16);
|
||||
assertArrayEquals(expectedOutput, out3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaximumRequestLength() {
|
||||
int expectedMax = 1024 * 1024;
|
||||
byte[] output = new byte[expectedMax + 123];
|
||||
FortunaGenerator f = new FortunaGenerator(new byte[32]);
|
||||
assertEquals(expectedMax, f.nextBytes(output, 0, output.length));
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
|
||||
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
|
||||
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class FortunaSecureRandomTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testClassPassesSelfTest() {
|
||||
assertTrue(FortunaSecureRandom.selfTest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelfTestVectorsAreReproducible() {
|
||||
byte[] key = new byte[32], seed = new byte[32];
|
||||
byte[] counter = new byte[16], output = new byte[16];
|
||||
byte[] newKey = new byte[32];
|
||||
// Calculate the initial key
|
||||
DoubleDigest digest = new DoubleDigest(new SHA256Digest());
|
||||
digest.update(key);
|
||||
digest.update(seed);
|
||||
digest.digest(key, 0, 32);
|
||||
// Calculate the first output block and the new key
|
||||
BlockCipher c = new AESLightEngine();
|
||||
c.init(true, new KeyParameter(key));
|
||||
counter[0] = 1;
|
||||
c.processBlock(counter, 0, output, 0);
|
||||
counter[0] = 2;
|
||||
c.processBlock(counter, 0, newKey, 0);
|
||||
counter[0] = 3;
|
||||
c.processBlock(counter, 0, newKey, 16);
|
||||
System.arraycopy(newKey, 0, key, 0, 32);
|
||||
// The first self-test vector should match the first output block
|
||||
assertArrayEquals(SELF_TEST_VECTOR_1, output);
|
||||
// Calculate the second output block and the new key before reseeding
|
||||
c.init(true, new KeyParameter(key));
|
||||
counter[0] = 4;
|
||||
c.processBlock(counter, 0, output, 0);
|
||||
counter[0] = 5;
|
||||
c.processBlock(counter, 0, newKey, 0);
|
||||
counter[0] = 6;
|
||||
c.processBlock(counter, 0, newKey, 16);
|
||||
System.arraycopy(newKey, 0, key, 0, 32);
|
||||
// The second self-test vector should match the second output block
|
||||
assertArrayEquals(SELF_TEST_VECTOR_2, output);
|
||||
// Calculate the new key after reseeding
|
||||
digest.update(key);
|
||||
digest.update(seed);
|
||||
digest.digest(key, 0, 32);
|
||||
// Calculate the third output block
|
||||
c.init(true, new KeyParameter(key));
|
||||
counter[0] = 8;
|
||||
c.processBlock(counter, 0, output, 0);
|
||||
// The third self-test vector should match the third output block
|
||||
assertArrayEquals(SELF_TEST_VECTOR_3, output);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -21,7 +21,7 @@ public class HashTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes2 = new byte[0];
|
||||
|
||||
public HashTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
@@ -14,8 +14,9 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDeriveMasterSecret() throws Exception {
|
||||
SeedProvider seedProvider = new TestSeedProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
|
||||
SecureRandomProvider
|
||||
secureRandomProvider = new TestSecureRandomProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
byte[] aPub = aPair.getPublic().getEncoded();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
@@ -27,8 +28,9 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDeriveSharedSecret() throws Exception {
|
||||
SeedProvider seedProvider = new TestSeedProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
|
||||
SecureRandomProvider
|
||||
secureRandomProvider = new TestSecureRandomProvider();
|
||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
byte[] aPub = aPair.getPublic().getEncoded();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class KeyDerivationTest extends BrambleTestCase {
|
||||
|
||||
@@ -23,7 +27,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
private final SecretKey master;
|
||||
|
||||
public KeyDerivationTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
master = TestUtils.getSecretKey();
|
||||
}
|
||||
|
||||
@@ -156,11 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private void assertAllDifferent(List<SecretKey> keys) {
|
||||
for (SecretKey ki : keys) {
|
||||
for (SecretKey kj : keys) {
|
||||
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
|
||||
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
|
||||
}
|
||||
}
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue;
|
||||
public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponentImpl crypto =
|
||||
new CryptoComponentImpl(new TestSeedProvider());
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
|
||||
@Test
|
||||
public void testAgreementPublicKeyLength() throws Exception {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MacTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes2 = new byte[0];
|
||||
|
||||
public MacTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue;
|
||||
public class PasswordBasedKdfTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponentImpl crypto =
|
||||
new CryptoComponentImpl(new TestSeedProvider());
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
|
||||
@Test
|
||||
public void testEncryptionAndDecryption() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.NONE;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -12,7 +13,7 @@ public class PasswordStrengthEstimatorImplTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testWeakPasswords() {
|
||||
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
|
||||
assertTrue(e.estimateStrength("") < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("") == NONE);
|
||||
assertTrue(e.estimateStrength("password") < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("letmein") < QUITE_STRONG);
|
||||
assertTrue(e.estimateStrength("123456") < QUITE_STRONG);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
|
||||
class PseudoSecureRandom extends SecureRandom {
|
||||
|
||||
private static final Provider PROVIDER = new PseudoSecureRandomProvider();
|
||||
|
||||
PseudoSecureRandom(byte[] seed) {
|
||||
super(new PseudoSecureRandomSpi(seed), PROVIDER);
|
||||
}
|
||||
|
||||
private static class PseudoSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final PseudoRandom pseudoRandom;
|
||||
|
||||
private PseudoSecureRandomSpi(byte[] seed) {
|
||||
pseudoRandom = new PseudoRandomImpl(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int length) {
|
||||
return pseudoRandom.nextBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
byte[] random = pseudoRandom.nextBytes(b.length);
|
||||
System.arraycopy(random, 0, b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
// Thank you for your input
|
||||
}
|
||||
}
|
||||
|
||||
private static class PseudoSecureRandomProvider extends Provider {
|
||||
|
||||
private PseudoSecureRandomProvider() {
|
||||
super("PseudoSecureRandom", 1.0, "Only for testing");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.crypto;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSeedProvider;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class SignatureTest extends BrambleTestCase {
|
||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||
|
||||
public SignatureTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSeedProvider());
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
KeyPair k = crypto.generateSignatureKeyPair();
|
||||
publicKey = k.getPublic().getEncoded();
|
||||
privateKey = k.getPrivate().getEncoded();
|
||||
|
||||
@@ -9,9 +9,13 @@ import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
|
||||
@@ -30,6 +34,58 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
|
||||
payload = TestUtils.getRandomBytes(payloadLength);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRejectsNegativePayloadLength() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, -1, 0, false);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRejectsNegativePaddingLength() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
s.writeFrame(payload, 0, -1, false);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRejectsMaxPayloadPlusPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH + 1];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMaxPayloadIncludingPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH - 1, 1, false);
|
||||
assertEquals(TAG_LENGTH + STREAM_HEADER_LENGTH + MAX_FRAME_LENGTH,
|
||||
out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMaxPayloadWithoutPadding() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
|
||||
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
|
||||
|
||||
byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
|
||||
s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 0, false);
|
||||
assertEquals(TAG_LENGTH + STREAM_HEADER_LENGTH + MAX_FRAME_LENGTH,
|
||||
out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.briarproject.bramble.properties;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class TransportPropertyManagerImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,12 @@ import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -38,8 +38,18 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
|
||||
public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
private final MessageValidator validator =
|
||||
context.mock(MessageValidator.class);
|
||||
private final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
|
||||
private final Executor dbExecutor = new ImmediateExecutor();
|
||||
private final Executor validationExecutor = new ImmediateExecutor();
|
||||
private final ClientId clientId =
|
||||
new ClientId(TestUtils.getRandomString(5));
|
||||
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
@@ -63,23 +73,58 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
private final MessageContext validResultWithDependencies =
|
||||
new MessageContext(metadata, Collections.singletonList(messageId1));
|
||||
|
||||
private ValidationManagerImpl vm;
|
||||
|
||||
public ValidationManagerImplTest() {
|
||||
// Encode the messages
|
||||
System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor,
|
||||
messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAndStop() throws Exception {
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// validateOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// deliverOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
// shareOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getMessagesToShare(txn2, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
}});
|
||||
|
||||
vm.startService();
|
||||
vm.stopService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesAreValidatedAtStartup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, false);
|
||||
@@ -87,6 +132,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
final Transaction txn4 = new Transaction(null, false);
|
||||
final Transaction txn5 = new Transaction(null, true);
|
||||
final Transaction txn6 = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -165,26 +211,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn6);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.startService();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingMessagesAreDeliveredAtStartup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, false);
|
||||
@@ -266,26 +297,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn4);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.startService();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesAreSharedAtStartup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
@@ -333,29 +349,15 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn4);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.startService();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessagesAreShared() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
final Transaction txn2 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -396,33 +398,19 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn2);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationContinuesAfterNoSuchMessageException()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
final Transaction txn3 = new Transaction(null, false);
|
||||
final Transaction txn4 = new Transaction(null, true);
|
||||
final Transaction txn5 = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -481,33 +469,19 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn5);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.startService();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationContinuesAfterNoSuchGroupException()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, true);
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
final Transaction txn3 = new Transaction(null, false);
|
||||
final Transaction txn4 = new Transaction(null, true);
|
||||
final Transaction txn5 = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -571,28 +545,14 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn5);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.startService();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonLocalMessagesAreValidatedWhenAdded() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -619,51 +579,20 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn1);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalMessagesAreNotValidatedWhenAdded() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, null));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesWithUndeliveredDependenciesArePending()
|
||||
throws Exception {
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -688,29 +617,15 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn1);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesWithDeliveredDependenciesGetDelivered()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -741,30 +656,16 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn1);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessagesWithInvalidDependenciesAreInvalid()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
final Transaction txn2 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -809,26 +710,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn2);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecursiveInvalidation() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
final Map<MessageId, State> twoDependents =
|
||||
@@ -842,6 +728,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
final Transaction txn4 = new Transaction(null, false);
|
||||
final Transaction txn5 = new Transaction(null, false);
|
||||
final Transaction txn6 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -927,26 +814,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn6);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingDependentsGetDelivered() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
final Message message3 = new Message(messageId3, groupId, timestamp,
|
||||
@@ -968,6 +840,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
final Transaction txn4 = new Transaction(null, false);
|
||||
final Transaction txn5 = new Transaction(null, false);
|
||||
final Transaction txn6 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -1100,26 +973,11 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn6);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyReadyPendingDependentsGetDelivered() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Map<MessageId, State> twoDependencies =
|
||||
new LinkedHashMap<MessageId, State>();
|
||||
twoDependencies.put(messageId, DELIVERED);
|
||||
@@ -1127,6 +985,7 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
final Transaction txn2 = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -1162,86 +1021,6 @@ public class ValidationManagerImplTest extends BrambleTestCase {
|
||||
oneOf(db).endTransaction(txn2);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageDependencyCycle() throws Exception {
|
||||
final MessageContext cycleContext = new MessageContext(metadata,
|
||||
Collections.singletonList(messageId));
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Executor dbExecutor = new ImmediateExecutor();
|
||||
final Executor cryptoExecutor = new ImmediateExecutor();
|
||||
final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
final MessageValidator validator = context.mock(MessageValidator.class);
|
||||
final IncomingMessageHook hook =
|
||||
context.mock(IncomingMessageHook.class);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn1 = new Transaction(null, false);
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
final Transaction txn3 = new Transaction(null, false);
|
||||
context.checking(new Expectations() {{
|
||||
// Load the group
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getGroup(txn, groupId);
|
||||
will(returnValue(group));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// Validate the message: valid
|
||||
oneOf(validator).validateMessage(message, group);
|
||||
will(returnValue(validResultWithDependencies));
|
||||
// Store the validation result
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).addMessageDependencies(txn1, message,
|
||||
validResultWithDependencies.getDependencies());
|
||||
oneOf(db).getMessageDependencies(txn1, messageId);
|
||||
will(returnValue(Collections.singletonMap(messageId1, UNKNOWN)));
|
||||
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||
oneOf(db).setMessageState(txn1, messageId, PENDING);
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
// Second message is coming in
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getGroup(txn2, groupId);
|
||||
will(returnValue(group));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
// Validate the message: valid
|
||||
oneOf(validator).validateMessage(message1, group);
|
||||
will(returnValue(cycleContext));
|
||||
// Store the validation result
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn3));
|
||||
oneOf(db).addMessageDependencies(txn3, message1,
|
||||
cycleContext.getDependencies());
|
||||
oneOf(db).getMessageDependencies(txn3, messageId1);
|
||||
will(returnValue(Collections.singletonMap(messageId, PENDING)));
|
||||
oneOf(db).mergeMessageMetadata(txn3, messageId1, metadata);
|
||||
oneOf(db).setMessageState(txn3, messageId1, PENDING);
|
||||
oneOf(db).commitTransaction(txn3);
|
||||
oneOf(db).endTransaction(txn3);
|
||||
}});
|
||||
|
||||
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
|
||||
cryptoExecutor, messageFactory);
|
||||
vm.registerMessageValidator(clientId, validator);
|
||||
vm.registerIncomingMessageHook(clientId, hook);
|
||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||
vm.eventOccurred(new MessageAddedEvent(message1, contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
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.security.Provider;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LinuxSecureRandomProviderTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProviderWritesToRandomDeviceOnFirstCall()
|
||||
throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the provider's output to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
|
||||
// Getting a provider should write entropy to the file
|
||||
Provider provider = p.getProvider();
|
||||
assertNotNull(provider);
|
||||
assertEquals("LinuxPRNG", provider.getName());
|
||||
// There should be at least 16 bytes from the clock, 8 from the runtime
|
||||
long length = urandom.length();
|
||||
assertTrue(length >= 24);
|
||||
// Getting another provider should not write to the file again
|
||||
provider = p.getProvider();
|
||||
assertNotNull(provider);
|
||||
assertEquals("LinuxPRNG", provider.getName());
|
||||
assertEquals(length, urandom.length());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
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.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class LinuxSecureRandomSpiTest extends BrambleTestCase {
|
||||
|
||||
private static final File RANDOM_DEVICE = new File("/dev/urandom");
|
||||
private static final int SEED_BYTES = 32;
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedsAreDistinct() {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
Set<Bytes> seeds = new HashSet<Bytes>();
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
|
||||
assertEquals(SEED_BYTES, seed.length);
|
||||
assertTrue(seeds.add(new Bytes(seed)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineSetSeedWritesToRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the engine's output to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
// Generate a seed
|
||||
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Check that the engine writes the seed to the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
|
||||
urandom);
|
||||
engine.engineSetSeed(seed);
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
byte[] written = new byte[SEED_BYTES];
|
||||
FileInputStream in = new FileInputStream(urandom);
|
||||
IoUtils.read(in, written);
|
||||
in.close();
|
||||
assertArrayEquals(seed, written);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate some entropy
|
||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(entropy);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the engine reads from the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
||||
RANDOM_DEVICE);
|
||||
byte[] b = new byte[SEED_BYTES];
|
||||
engine.engineNextBytes(b);
|
||||
assertArrayEquals(entropy, b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate some entropy
|
||||
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(entropy);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the engine reads from the file
|
||||
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
|
||||
RANDOM_DEVICE);
|
||||
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
|
||||
assertArrayEquals(entropy, b);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
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.FileOutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.briarproject.bramble.api.system.SeedProvider.SEED_BYTES;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LinuxSeedProviderTest extends BrambleTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedAppearsSane() {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
Set<Bytes> seeds = new HashSet<Bytes>();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
byte[] seed = p.getSeed();
|
||||
assertEquals(SEED_BYTES, seed.length);
|
||||
assertTrue(seeds.add(new Bytes(seed)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntropyIsWrittenToPool() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Redirect the provider's entropy to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
assertTrue(urandom.createNewFile());
|
||||
assertEquals(0, urandom.length());
|
||||
String path = urandom.getAbsolutePath();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider(path, "/dev/urandom");
|
||||
p.getSeed();
|
||||
// There should be 16 bytes from the clock, plus network interfaces
|
||||
assertTrue(urandom.length() > 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeedIsReadFromPool() throws Exception {
|
||||
if (!(OsUtils.isLinux())) {
|
||||
System.err.println("WARNING: Skipping test, can't run on this OS");
|
||||
return;
|
||||
}
|
||||
// Generate a seed
|
||||
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
|
||||
// Write the seed to a file
|
||||
File urandom = new File(testDir, "urandom");
|
||||
urandom.delete();
|
||||
FileOutputStream out = new FileOutputStream(urandom);
|
||||
out.write(seed);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertTrue(urandom.exists());
|
||||
assertEquals(SEED_BYTES, urandom.length());
|
||||
// Check that the provider reads the seed from the file
|
||||
String path = urandom.getAbsolutePath();
|
||||
LinuxSeedProvider p = new LinuxSeedProvider("/dev/urandom", path);
|
||||
assertArrayEquals(seed, p.getSeed());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestSecureRandomProvider implements SecureRandomProvider {
|
||||
|
||||
@Override
|
||||
public Provider getProvider() {
|
||||
// Use the default provider
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TestSeedProvider implements SeedProvider {
|
||||
|
||||
@Override
|
||||
public byte[] getSeed() {
|
||||
return TestUtils.getRandomBytes(32);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class TestSeedProviderModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SeedProvider provideSeedProvider() {
|
||||
return new TestSeedProvider();
|
||||
SecureRandomProvider provideSeedProvider() {
|
||||
return new TestSecureRandomProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class DesktopSecureRandomModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SecureRandomProvider provideSecureRandomProvider() {
|
||||
return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class DesktopSeedProviderModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SeedProvider provideSeedProvider() {
|
||||
return OsUtils.isLinux() ? new LinuxSeedProvider() : null;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
<application
|
||||
android:name=".android.BriarApplicationImpl"
|
||||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:icon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/BriarTheme">
|
||||
@@ -333,7 +333,8 @@
|
||||
<activity
|
||||
android:name=".android.settings.SettingsActivity"
|
||||
android:label="@string/settings_button"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
@@ -356,13 +357,12 @@
|
||||
|
||||
<activity
|
||||
android:name=".android.panic.PanicPreferencesActivity"
|
||||
android:label="@string/panic_setting">
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.CONNECT"/>
|
||||
<action android:name="info.guardianproject.panic.action.DISCONNECT"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
android:label="@string/panic_setting"
|
||||
android:parentActivityName=".android.settings.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.settings.SettingsActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.android.ReferenceManager;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
@@ -43,6 +44,8 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -87,6 +90,8 @@ public interface AndroidComponent
|
||||
|
||||
AndroidNotificationManager androidNotificationManager();
|
||||
|
||||
ScreenFilterMonitor screenFilterMonitor();
|
||||
|
||||
ConnectionRegistry connectionRegistry();
|
||||
|
||||
ContactManager contactManager();
|
||||
@@ -138,10 +143,14 @@ public interface AndroidComponent
|
||||
@IoExecutor
|
||||
Executor ioExecutor();
|
||||
|
||||
void inject(BriarService activity);
|
||||
void inject(BriarService briarService);
|
||||
|
||||
void inject(BriarReportSender briarReportSender);
|
||||
|
||||
void inject(EmojiProvider emojiProvider);
|
||||
|
||||
void inject(RecentEmojiPageModel recentEmojiPageModel);
|
||||
|
||||
// Eager singleton load
|
||||
void inject(AppModule.EagerSingletons init);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.UiThread;
|
||||
@@ -73,8 +70,6 @@ import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_CONTACTS;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_FORUMS;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_GROUPS;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_NOTIFY_BLOG;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_NOTIFY_GROUP;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
|
||||
@ThreadSafe
|
||||
@@ -92,25 +87,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
// Content URIs to differentiate between pending intents
|
||||
private static final String CONTACT_URI =
|
||||
"content://org.briarproject/contact";
|
||||
"content://org.briarproject.briar/contact";
|
||||
private static final String GROUP_URI =
|
||||
"content://org.briarproject/group";
|
||||
"content://org.briarproject.briar/group";
|
||||
private static final String FORUM_URI =
|
||||
"content://org.briarproject/forum";
|
||||
"content://org.briarproject.briar/forum";
|
||||
private static final String BLOG_URI =
|
||||
"content://org.briarproject/blog";
|
||||
|
||||
// Actions for intents that are broadcast when notifications are dismissed
|
||||
private static final String CLEAR_PRIVATE_MESSAGE_ACTION =
|
||||
"org.briarproject.briar.CLEAR_PRIVATE_MESSAGE_NOTIFICATION";
|
||||
private static final String CLEAR_GROUP_ACTION =
|
||||
"org.briarproject.briar.CLEAR_GROUP_NOTIFICATION";
|
||||
private static final String CLEAR_FORUM_ACTION =
|
||||
"org.briarproject.briar.CLEAR_FORUM_NOTIFICATION";
|
||||
private static final String CLEAR_BLOG_ACTION =
|
||||
"org.briarproject.briar.CLEAR_BLOG_NOTIFICATION";
|
||||
private static final String CLEAR_INTRODUCTION_ACTION =
|
||||
"org.briarproject.briar.CLEAR_INTRODUCTION_NOTIFICATION";
|
||||
"content://org.briarproject.briar/blog";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
|
||||
@@ -119,7 +102,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private final SettingsManager settingsManager;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final BroadcastReceiver receiver = new DeleteIntentReceiver();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// The following must only be accessed on the main UI thread
|
||||
@@ -157,30 +139,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
} catch (DbException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
// Register a broadcast receiver for notifications being dismissed
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CLEAR_PRIVATE_MESSAGE_ACTION);
|
||||
filter.addAction(CLEAR_GROUP_ACTION);
|
||||
filter.addAction(CLEAR_FORUM_ACTION);
|
||||
filter.addAction(CLEAR_BLOG_ACTION);
|
||||
filter.addAction(CLEAR_INTRODUCTION_ACTION);
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
// Clear all notifications and unregister the broadcast receiver
|
||||
// Clear all notifications
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
@@ -189,7 +152,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
clearForumPostNotification();
|
||||
clearBlogPostNotification();
|
||||
clearIntroductionSuccessNotification();
|
||||
appContext.unregisterReceiver(receiver);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
@@ -325,27 +287,23 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private void updateContactNotification() {
|
||||
if (contactTotal == 0) {
|
||||
clearContactNotification();
|
||||
} else if (settings.getBoolean("notifyPrivateMessages", true)) {
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_message);
|
||||
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.private_message_notification_text, contactTotal,
|
||||
contactTotal));
|
||||
boolean sound = settings.getBoolean("notifySound", true);
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (sound && !StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
// Clear the counters if the notification is dismissed
|
||||
Intent clear = new Intent(CLEAR_PRIVATE_MESSAGE_ACTION);
|
||||
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
|
||||
clear, 0);
|
||||
b.setDeleteIntent(delete);
|
||||
if (contactCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant conversation
|
||||
Intent i = new Intent(appContext, ConversationActivity.class);
|
||||
@@ -381,11 +339,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@UiThread
|
||||
private int getDefaults() {
|
||||
int defaults = DEFAULT_LIGHTS;
|
||||
boolean sound = settings.getBoolean("notifySound", true);
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (sound && StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
defaults |= DEFAULT_SOUND;
|
||||
if (settings.getBoolean("notifyVibration", true))
|
||||
if (settings.getBoolean(PREF_NOTIFY_VIBRATION, true))
|
||||
defaults |= DEFAULT_VIBRATE;
|
||||
return defaults;
|
||||
}
|
||||
@@ -438,22 +396,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_group);
|
||||
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.group_message_notification_text, groupTotal,
|
||||
groupTotal));
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
// Clear the counters if the notification is dismissed
|
||||
Intent clear = new Intent(CLEAR_GROUP_ACTION);
|
||||
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
|
||||
clear, 0);
|
||||
b.setDeleteIntent(delete);
|
||||
if (groupCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant group
|
||||
Intent i = new Intent(appContext, GroupActivity.class);
|
||||
@@ -530,26 +484,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private void updateForumPostNotification() {
|
||||
if (forumTotal == 0) {
|
||||
clearForumPostNotification();
|
||||
} else if (settings.getBoolean("notifyForumPosts", true)) {
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_forum);
|
||||
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.forum_post_notification_text, forumTotal,
|
||||
forumTotal));
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
// Clear the counters if the notification is dismissed
|
||||
Intent clear = new Intent(CLEAR_FORUM_ACTION);
|
||||
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
|
||||
clear, 0);
|
||||
b.setDeleteIntent(delete);
|
||||
if (forumCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant forum
|
||||
Intent i = new Intent(appContext, ForumActivity.class);
|
||||
@@ -630,22 +580,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_blog);
|
||||
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.blog_post_notification_text, blogTotal,
|
||||
blogTotal));
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
// Clear the counters if the notification is dismissed
|
||||
Intent clear = new Intent(CLEAR_BLOG_ACTION);
|
||||
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
|
||||
clear, 0);
|
||||
b.setDeleteIntent(delete);
|
||||
// Touching the notification shows the combined blog feed
|
||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||
i.putExtra(INTENT_BLOGS, true);
|
||||
@@ -696,17 +642,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.introduction_notification_text, introductionTotal,
|
||||
introductionTotal));
|
||||
String ringtoneUri = settings.get("notifyRingtoneUri");
|
||||
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
// Clear the counter if the notification is dismissed
|
||||
Intent clear = new Intent(CLEAR_INTRODUCTION_ACTION);
|
||||
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
|
||||
clear, 0);
|
||||
b.setDeleteIntent(delete);
|
||||
// Touching the notification shows the contact list
|
||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||
i.putExtra(INTENT_CONTACTS, true);
|
||||
@@ -847,27 +788,4 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
});
|
||||
}
|
||||
|
||||
private class DeleteIntentReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (CLEAR_PRIVATE_MESSAGE_ACTION.equals(action)) {
|
||||
clearContactNotification();
|
||||
} else if (CLEAR_GROUP_ACTION.equals(action)) {
|
||||
clearGroupMessageNotification();
|
||||
} else if (CLEAR_FORUM_ACTION.equals(action)) {
|
||||
clearForumPostNotification();
|
||||
} else if (CLEAR_BLOG_ACTION.equals(action)) {
|
||||
clearBlogPostNotification();
|
||||
} else if (CLEAR_INTRODUCTION_ACTION.equals(action)) {
|
||||
clearIntroductionSuccessNotification();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.ui.UiCallback;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.android.ReferenceManager;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -37,6 +38,8 @@ public class AppModule {
|
||||
static class EagerSingletons {
|
||||
@Inject
|
||||
AndroidNotificationManager androidNotificationManager;
|
||||
@Inject
|
||||
ScreenFilterMonitor screenFilterMonitor;
|
||||
}
|
||||
|
||||
private final Application application;
|
||||
@@ -166,4 +169,12 @@ public class AppModule {
|
||||
eventBus.addListener(notificationManager);
|
||||
return notificationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
LifecycleManager lifecycleManager, ScreenFilterMonitorImpl sfm) {
|
||||
lifecycleManager.registerService(sfm);
|
||||
return sfm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
||||
implements Service,
|
||||
ScreenFilterMonitor {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||
private static final String PREF_SCREEN_FILTER_APPS =
|
||||
"shownScreenFilterApps";
|
||||
private final Context appContext;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final LinkedList<String> appNames = new LinkedList<>();
|
||||
private final PackageManager pm;
|
||||
private final SharedPreferences prefs;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final Set<String> apps = new HashSet<>();
|
||||
private final Set<String> shownApps;
|
||||
// Used solely for the UiThread
|
||||
private boolean serviceStarted = false;
|
||||
|
||||
@Inject
|
||||
ScreenFilterMonitorImpl(AndroidExecutor executor, Application app) {
|
||||
this.androidExecutor = executor;
|
||||
this.appContext = app;
|
||||
pm = appContext.getPackageManager();
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
shownApps = getShownScreenFilterApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
intentFilter.addDataScheme("package");
|
||||
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
|
||||
intentFilter);
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
serviceStarted = true;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
serviceStarted = false;
|
||||
appContext.unregisterReceiver(ScreenFilterMonitorImpl.this);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getShownScreenFilterApps() {
|
||||
// res must not be modified
|
||||
Set<String> s =
|
||||
prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
|
||||
HashSet<String> result = new HashSet<>();
|
||||
if (s != null) {
|
||||
result.addAll(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||
final String packageName =
|
||||
intent.getData().getEncodedSchemeSpecificPart();
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String pkg = isOverlayApp(packageName);
|
||||
if (pkg == null) {
|
||||
return;
|
||||
}
|
||||
apps.add(pkg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public Set<String> getApps() {
|
||||
if (!serviceStarted) {
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
}
|
||||
TreeSet<String> buf = new TreeSet<>();
|
||||
if (apps.isEmpty()) {
|
||||
return buf;
|
||||
}
|
||||
buf.addAll(apps);
|
||||
buf.removeAll(shownApps);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
|
||||
HashSet<String> buf = new HashSet(s);
|
||||
shownApps.addAll(buf);
|
||||
if (persistent && !s.isEmpty()) {
|
||||
buf.addAll(getShownScreenFilterApps());
|
||||
prefs.edit()
|
||||
.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getInstalledScreenFilterApps() {
|
||||
HashSet<String> screenFilterApps = new HashSet<>();
|
||||
List<PackageInfo> packageInfos =
|
||||
pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
for (PackageInfo packageInfo : packageInfos) {
|
||||
if (isOverlayApp(packageInfo)) {
|
||||
String name = pkgToString(packageInfo);
|
||||
if (name != null) {
|
||||
screenFilterApps.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return screenFilterApps;
|
||||
}
|
||||
|
||||
// Checks if pkg uses the SYSTEM_ALERT_WINDOW permission and if so
|
||||
// returns the app name.
|
||||
@Nullable
|
||||
private String isOverlayApp(String pkg) {
|
||||
try {
|
||||
PackageInfo pkgInfo =
|
||||
pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
|
||||
if (isOverlayApp(pkgInfo)) {
|
||||
return pkgToString(pkgInfo);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.warning("Package name not found: " + pkg);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch the application name for a given package.
|
||||
@Nullable
|
||||
private String pkgToString(PackageInfo pkgInfo) {
|
||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||
if (seq != null) {
|
||||
return seq.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks if an installed pkg is a user app using the permission.
|
||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||
int mask = ApplicationInfo.FLAG_SYSTEM |
|
||||
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
// Ignore system apps
|
||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
||||
return false;
|
||||
}
|
||||
//Get Permissions
|
||||
String[] requestedPermissions =
|
||||
packageInfo.requestedPermissions;
|
||||
if (requestedPermissions != null) {
|
||||
for (String requestedPermission : requestedPermissions) {
|
||||
if (requestedPermission
|
||||
.equals(SYSTEM_ALERT_WINDOW)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import org.briarproject.briar.BuildConfig;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
@@ -11,7 +13,7 @@ public interface TestingConstants {
|
||||
* Whether this is an alpha or beta build. This should be set to false for
|
||||
* release builds.
|
||||
*/
|
||||
boolean TESTING = true;
|
||||
boolean TESTING = BuildConfig.DEBUG;
|
||||
|
||||
/** Default log level. */
|
||||
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;
|
||||
|
||||
@@ -64,8 +64,6 @@ import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
|
||||
import org.briarproject.briar.android.sharing.SharingModule;
|
||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@@ -151,10 +149,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(RssFeedManageActivity activity);
|
||||
|
||||
void inject(EmojiProvider emojiProvider);
|
||||
|
||||
void inject(RecentEmojiPageModel recentEmojiPageModel);
|
||||
|
||||
// Fragments
|
||||
void inject(ContactListFragment fragment);
|
||||
|
||||
|
||||
@@ -13,11 +13,15 @@ import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.SFDialogFragment;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
@@ -25,13 +29,16 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity
|
||||
implements DestroyableContext {
|
||||
|
||||
protected ActivityComponent activityComponent;
|
||||
|
||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||
new ArrayList<>();
|
||||
private boolean destroyed = false;
|
||||
|
||||
@Inject
|
||||
protected ScreenFilterMonitor screenFilterMonitor;
|
||||
private SFDialogFragment dialogFrag;
|
||||
|
||||
public abstract void injectActivity(ActivityComponent component);
|
||||
|
||||
public void addLifecycleController(ActivityLifecycleController alc) {
|
||||
@@ -58,6 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||
alc.onActivityCreate(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ActivityComponent getActivityComponent() {
|
||||
@@ -89,6 +97,35 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
showNewScreenFilterWarning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (dialogFrag != null) {
|
||||
dialogFrag.dismiss();
|
||||
dialogFrag = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void showNewScreenFilterWarning() {
|
||||
final Set<String> apps = screenFilterMonitor.getApps();
|
||||
if (apps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
|
||||
dialogFrag.setCancelable(false);
|
||||
dialogFrag.show(getSupportFragmentManager(), "SFDialog");
|
||||
}
|
||||
|
||||
public void rememberShownApps(ArrayList<String> s, boolean permanent) {
|
||||
screenFilterMonitor.storeAppsAsShown(s, permanent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
@@ -14,7 +14,7 @@ import android.view.Window;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.DbController;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.briar.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.login.PasswordActivity;
|
||||
import org.briarproject.briar.android.panic.ExitActivity;
|
||||
@@ -27,7 +27,6 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
|
||||
@SuppressLint("Registered")
|
||||
@@ -61,7 +60,6 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
super.onStart();
|
||||
if (!briarController.hasEncryptionKey() && !isFinishing()) {
|
||||
Intent i = new Intent(this, PasswordActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivityForResult(i, REQUEST_PASSWORD);
|
||||
}
|
||||
}
|
||||
@@ -118,17 +116,28 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
protected void signOut(final boolean removeFromRecentApps) {
|
||||
briarController.signOut(new UiResultHandler<Void>(this) {
|
||||
@Override
|
||||
public void onResultUi(Void result) {
|
||||
if (removeFromRecentApps) startExitActivity();
|
||||
else finishAndExit();
|
||||
}
|
||||
});
|
||||
if (briarController.hasEncryptionKey()) {
|
||||
// Don't use UiResultHandler because we want the result even if
|
||||
// this activity has been destroyed
|
||||
briarController.signOut(new ResultHandler<Void>() {
|
||||
@Override
|
||||
public void onResult(Void result) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
exit(removeFromRecentApps);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
exit(removeFromRecentApps);
|
||||
}
|
||||
}
|
||||
|
||||
protected void signOut() {
|
||||
signOut(false);
|
||||
private void exit(boolean removeFromRecentApps) {
|
||||
if (removeFromRecentApps) startExitActivity();
|
||||
else finishAndExit();
|
||||
}
|
||||
|
||||
private void startExitActivity() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
import org.briarproject.briar.util.HtmlUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -33,6 +34,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -229,6 +231,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
|
||||
return header;
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
|
||||
String body;
|
||||
if (h instanceof BlogCommentHeader) {
|
||||
@@ -243,10 +246,11 @@ abstract class BaseControllerImpl extends DbControllerImpl
|
||||
}
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private String getPostBody(MessageId m) throws DbException {
|
||||
String body = bodyCache.get(m);
|
||||
if (body == null) {
|
||||
body = blogManager.getPostBody(m);
|
||||
body = HtmlUtils.clean(blogManager.getPostBody(m), ARTICLE);
|
||||
bodyCache.put(m, body);
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class SFDialogFragment extends DialogFragment {
|
||||
|
||||
public static SFDialogFragment newInstance(ArrayList<String> apps) {
|
||||
SFDialogFragment frag = new SFDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArrayList("apps", apps);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(
|
||||
getActivity(),
|
||||
R.style.BriarDialogThemeNoFilter);
|
||||
builder.setTitle(R.string.screen_filter_title);
|
||||
LayoutInflater li = getActivity().getLayoutInflater();
|
||||
//Pass null here because it's an AlertDialog
|
||||
View v =
|
||||
li.inflate(R.layout.alert_dialog_checkbox, null,
|
||||
false);
|
||||
TextView t = (TextView) v.findViewById(R.id.alert_dialog_text);
|
||||
final ArrayList<String> apps =
|
||||
getArguments().getStringArrayList("apps");
|
||||
t.setText(getString(R.string.screen_filter_body, TextUtils
|
||||
.join("\n", apps)));
|
||||
final CheckBox cb =
|
||||
(CheckBox) v.findViewById(
|
||||
R.id.checkBox_screen_filter_reminder);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
((BaseActivity) getActivity())
|
||||
.rememberShownApps(apps, cb.isChecked());
|
||||
}
|
||||
});
|
||||
builder.setView(v);
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
|
||||
public class ChangePasswordActivity extends BaseActivity
|
||||
implements OnClickListener, OnEditorActionListener {
|
||||
@@ -109,13 +109,13 @@ public class ChangePasswordActivity extends BaseActivity
|
||||
strengthMeter.setStrength(strength);
|
||||
UiUtils.setError(newPasswordEntryWrapper,
|
||||
getString(R.string.password_too_weak),
|
||||
firstPassword.length() > 0 && strength < WEAK);
|
||||
firstPassword.length() > 0 && strength < QUITE_WEAK);
|
||||
UiUtils.setError(newPasswordConfirmationWrapper,
|
||||
getString(R.string.passwords_do_not_match),
|
||||
secondPassword.length() > 0 && !passwordsMatch);
|
||||
changePasswordButton.setEnabled(
|
||||
!currentPassword.getText().toString().isEmpty() &&
|
||||
passwordsMatch && strength >= WEAK);
|
||||
passwordsMatch && strength >= QUITE_WEAK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.widget.TextView.OnEditorActionListener;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
@@ -26,13 +27,18 @@ import javax.inject.Inject;
|
||||
|
||||
import static android.content.Intent.ACTION_MAIN;
|
||||
import static android.content.Intent.CATEGORY_HOME;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
public class PasswordActivity extends BaseActivity {
|
||||
|
||||
@Inject
|
||||
protected PasswordController passwordController;
|
||||
PasswordController passwordController;
|
||||
|
||||
@Inject
|
||||
BriarController briarController;
|
||||
|
||||
private Button signInButton;
|
||||
private ProgressBar progress;
|
||||
@@ -82,6 +88,16 @@ public class PasswordActivity extends BaseActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
// If the user has already signed in, clean up this instance
|
||||
if (briarController.hasEncryptionKey()) {
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
@@ -98,8 +114,9 @@ public class PasswordActivity extends BaseActivity {
|
||||
private void deleteAccount() {
|
||||
passwordController.deleteAccount(this);
|
||||
setResult(RESULT_CANCELED);
|
||||
startActivity(new Intent(this, SetupActivity.class));
|
||||
supportFinishAfterTransition();
|
||||
Intent i = new Intent(this, SetupActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
public void onSignInClick(View v) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK;
|
||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
|
||||
public class SetupActivity extends BaseActivity implements OnClickListener,
|
||||
@@ -115,13 +115,13 @@ public class SetupActivity extends BaseActivity implements OnClickListener,
|
||||
nicknameLength > MAX_AUTHOR_NAME_LENGTH);
|
||||
UiUtils.setError(passwordEntryWrapper,
|
||||
getString(R.string.password_too_weak),
|
||||
firstPassword.length() > 0 && strength < WEAK);
|
||||
firstPassword.length() > 0 && strength < QUITE_WEAK);
|
||||
UiUtils.setError(passwordConfirmationWrapper,
|
||||
getString(R.string.passwords_do_not_match),
|
||||
secondPassword.length() > 0 && !passwordsMatch);
|
||||
createAccountButton.setEnabled(nicknameLength > 0
|
||||
&& nicknameLength <= MAX_AUTHOR_NAME_LENGTH
|
||||
&& passwordsMatch && strength >= WEAK);
|
||||
&& passwordsMatch && strength >= QUITE_WEAK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -188,7 +188,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||
getSupportFragmentManager()
|
||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||
/**
|
||||
/*
|
||||
* This Makes sure that the first fragment (ContactListFragment) the
|
||||
* user sees is the same as the last fragment the user sees before
|
||||
* exiting. This models the typical Google navigation behaviour such
|
||||
@@ -212,11 +212,10 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void signOut() {
|
||||
private void signOut() {
|
||||
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
||||
startFragment(new SignOutFragment());
|
||||
super.signOut();
|
||||
signOut(false);
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment, int itemId){
|
||||
|
||||
@@ -103,12 +103,14 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
||||
showPanicApp(packageName);
|
||||
|
||||
if (packageName.equals(Panic.PACKAGE_NAME_NONE)) {
|
||||
lockPref.setEnabled(false);
|
||||
purgePref.setChecked(false);
|
||||
purgePref.setEnabled(false);
|
||||
uninstallPref.setChecked(false);
|
||||
uninstallPref.setEnabled(false);
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
} else {
|
||||
lockPref.setEnabled(true);
|
||||
purgePref.setEnabled(true);
|
||||
uninstallPref.setEnabled(true);
|
||||
}
|
||||
@@ -126,7 +128,12 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(
|
||||
"market://details?id=info.guardianproject.ripple"));
|
||||
startActivity(intent);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (intent.resolveActivity(
|
||||
getActivity().getPackageManager()) !=
|
||||
null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -185,6 +192,8 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
||||
panicAppPref.setIcon(
|
||||
android.R.drawable.ic_menu_close_clear_cancel);
|
||||
|
||||
// disable panic actions
|
||||
lockPref.setEnabled(false);
|
||||
purgePref.setEnabled(false);
|
||||
uninstallPref.setEnabled(false);
|
||||
} else {
|
||||
@@ -196,6 +205,8 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
||||
panicAppPref.setIcon(
|
||||
pm.getApplicationIcon(triggerPackageName));
|
||||
|
||||
// enable panic actions
|
||||
lockPref.setEnabled(true);
|
||||
purgePref.setEnabled(true);
|
||||
uninstallPref.setEnabled(true);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
@@ -230,7 +241,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext(),
|
||||
R.style.BriarDialogTheme);
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.dialog_title_connect_panic_app));
|
||||
|
||||
CharSequence app = getString(R.string.unknown_app);
|
||||
|
||||
@@ -57,10 +57,9 @@ public class PanicResponderActivity extends BriarActivity {
|
||||
LOG.info("Received Panic Trigger...");
|
||||
|
||||
if (PanicResponder.receivedTriggerFromConnectedApp(this)) {
|
||||
LOG.info("Panic Trigger came from connected app.");
|
||||
LOG.info("Performing destructive responses...");
|
||||
LOG.info("Panic Trigger came from connected app");
|
||||
|
||||
// Performing destructive panic responses
|
||||
// Performing panic responses
|
||||
if (sharedPref.getBoolean(KEY_UNINSTALL, false)) {
|
||||
LOG.info("Purging all data...");
|
||||
deleteAllData();
|
||||
@@ -78,19 +77,6 @@ public class PanicResponderActivity extends BriarActivity {
|
||||
signOut(true);
|
||||
}
|
||||
}
|
||||
// Performing non-destructive default panic response
|
||||
else if (sharedPref.getBoolean(KEY_LOCK, true)) {
|
||||
LOG.info("Signing out...");
|
||||
signOut(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// received intent from non-trusted app
|
||||
else {
|
||||
intent = getIntent();
|
||||
if (intent != null && Panic.isTriggerIntent(intent)) {
|
||||
LOG.info("Signing out...");
|
||||
signOut(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
@@ -45,7 +47,18 @@ import static android.media.RingtoneManager.TYPE_NOTIFICATION;
|
||||
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -53,8 +66,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements EventListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
public static final String SETTINGS_NAMESPACE = "android-ui";
|
||||
public static final String PREF_NOTIFY_GROUP = "notifyGroupMessages";
|
||||
public static final String PREF_NOTIFY_BLOG = "notifyBlogPosts";
|
||||
public static final String BT_NAMESPACE = BluetoothConstants.ID.getString();
|
||||
public static final String TOR_NAMESPACE = TorConstants.ID.getString();
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SettingsFragment.class.getName());
|
||||
@@ -62,7 +75,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private SettingsActivity listener;
|
||||
private AndroidExecutor androidExecutor;
|
||||
private ListPreference enableBluetooth;
|
||||
private ListPreference torOverMobile;
|
||||
private ListPreference torNetwork;
|
||||
private CheckBoxPreference notifyPrivateMessages;
|
||||
private CheckBoxPreference notifyGroupMessages;
|
||||
private CheckBoxPreference notifyForumPosts;
|
||||
@@ -74,7 +87,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
private volatile SettingsManager settingsManager;
|
||||
private volatile EventBus eventBus;
|
||||
private volatile Settings settings;
|
||||
private volatile boolean bluetoothSetting = false, torSetting = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -90,10 +102,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
public void onCreatePreferences(Bundle bundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
|
||||
enableBluetooth =
|
||||
(ListPreference) findPreference("pref_key_bluetooth");
|
||||
torOverMobile =
|
||||
(ListPreference) findPreference("pref_key_tor_mobile");
|
||||
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
|
||||
torNetwork = (ListPreference) findPreference("pref_key_tor_network");
|
||||
notifyPrivateMessages = (CheckBoxPreference) findPreference(
|
||||
"pref_key_notify_private_messages");
|
||||
notifyGroupMessages = (CheckBoxPreference) findPreference(
|
||||
@@ -107,7 +117,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
notifySound = findPreference("pref_key_notify_sound");
|
||||
|
||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||
torOverMobile.setOnPreferenceChangeListener(this);
|
||||
torNetwork.setOnPreferenceChangeListener(this);
|
||||
notifyPrivateMessages.setOnPreferenceChangeListener(this);
|
||||
notifyGroupMessages.setOnPreferenceChangeListener(this);
|
||||
notifyForumPosts.setOnPreferenceChangeListener(this);
|
||||
@@ -126,10 +136,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI,
|
||||
DEFAULT_NOTIFICATION_URI);
|
||||
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
|
||||
if (settings.getBoolean("notifySound", true)) {
|
||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||
Uri uri;
|
||||
String ringtoneUri =
|
||||
settings.get("notifyRingtoneUri");
|
||||
settings.get(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
uri = DEFAULT_NOTIFICATION_URI;
|
||||
else uri = Uri.parse(ringtoneUri);
|
||||
@@ -181,14 +191,18 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
Settings btSettings = settingsManager.getSettings("bt");
|
||||
Settings torSettings = settingsManager.getSettings("tor");
|
||||
Settings btSettings =
|
||||
settingsManager.getSettings(BT_NAMESPACE);
|
||||
Settings torSettings =
|
||||
settingsManager.getSettings(TOR_NAMESPACE);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading settings took " + duration + " ms");
|
||||
bluetoothSetting = btSettings.getBoolean("enable", false);
|
||||
torSetting = torSettings.getBoolean("torOverMobile", true);
|
||||
displaySettings();
|
||||
boolean btSetting =
|
||||
btSettings.getBoolean(PREF_BT_ENABLE, false);
|
||||
int torSetting = torSettings.getInt(PREF_TOR_NETWORK,
|
||||
PREF_TOR_NETWORK_ALWAYS);
|
||||
displaySettings(btSetting, torSetting);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -197,31 +211,33 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
});
|
||||
}
|
||||
|
||||
private void displaySettings() {
|
||||
private void displaySettings(final boolean btSetting,
|
||||
final int torSetting) {
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
enableBluetooth.setValue(Boolean.toString(bluetoothSetting));
|
||||
torOverMobile.setValue(Boolean.toString(torSetting));
|
||||
enableBluetooth.setValue(Boolean.toString(btSetting));
|
||||
torNetwork.setValue(Integer.toString(torSetting));
|
||||
|
||||
notifyPrivateMessages.setChecked(settings.getBoolean(
|
||||
"notifyPrivateMessages", true));
|
||||
PREF_NOTIFY_PRIVATE, true));
|
||||
|
||||
notifyGroupMessages.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_GROUP, true));
|
||||
|
||||
notifyForumPosts.setChecked(settings.getBoolean(
|
||||
"notifyForumPosts", true));
|
||||
PREF_NOTIFY_FORUM, true));
|
||||
|
||||
notifyBlogPosts.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_BLOG, true));
|
||||
|
||||
notifyVibration.setChecked(settings.getBoolean(
|
||||
"notifyVibration", true));
|
||||
PREF_NOTIFY_VIBRATION, true));
|
||||
|
||||
String text;
|
||||
if (settings.getBoolean("notifySound", true)) {
|
||||
String ringtoneName = settings.get("notifyRingtoneName");
|
||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||
String ringtoneName =
|
||||
settings.get(PREF_NOTIFY_RINGTONE_NAME);
|
||||
if (StringUtils.isNullOrEmpty(ringtoneName)) {
|
||||
text = getString(R.string.notify_sound_setting_default);
|
||||
} else {
|
||||
@@ -248,15 +264,15 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
if (preference == enableBluetooth) {
|
||||
bluetoothSetting = Boolean.valueOf((String) o);
|
||||
enableOrDisableBluetooth(bluetoothSetting);
|
||||
storeBluetoothSettings();
|
||||
} else if (preference == torOverMobile) {
|
||||
torSetting = Boolean.valueOf((String) o);
|
||||
storeTorSettings();
|
||||
boolean btSetting = Boolean.valueOf((String) o);
|
||||
enableOrDisableBluetooth(btSetting);
|
||||
storeBluetoothSettings(btSetting);
|
||||
} else if (preference == torNetwork) {
|
||||
int torSetting = Integer.valueOf((String) o);
|
||||
storeTorSettings(torSetting);
|
||||
} else if (preference == notifyPrivateMessages) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean("notifyPrivateMessages", (Boolean) o);
|
||||
s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) o);
|
||||
storeSettings(s);
|
||||
} else if (preference == notifyGroupMessages) {
|
||||
Settings s = new Settings();
|
||||
@@ -264,7 +280,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
storeSettings(s);
|
||||
} else if (preference == notifyForumPosts) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean("notifyForumPosts", (Boolean) o);
|
||||
s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) o);
|
||||
storeSettings(s);
|
||||
} else if (preference == notifyBlogPosts) {
|
||||
Settings s = new Settings();
|
||||
@@ -272,7 +288,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
storeSettings(s);
|
||||
} else if (preference == notifyVibration) {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean("notifyVibration", (Boolean) o);
|
||||
s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) o);
|
||||
storeSettings(s);
|
||||
}
|
||||
return true;
|
||||
@@ -291,15 +307,15 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
}
|
||||
}
|
||||
|
||||
private void storeTorSettings() {
|
||||
private void storeTorSettings(final int torSetting) {
|
||||
listener.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean("torOverMobile", torSetting);
|
||||
s.putInt(PREF_TOR_NETWORK, torSetting);
|
||||
long now = System.currentTimeMillis();
|
||||
settingsManager.mergeSettings(s, "tor");
|
||||
settingsManager.mergeSettings(s, TOR_NAMESPACE);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Merging settings took " + duration + " ms");
|
||||
@@ -311,15 +327,15 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
});
|
||||
}
|
||||
|
||||
private void storeBluetoothSettings() {
|
||||
private void storeBluetoothSettings(final boolean btSetting) {
|
||||
listener.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean("enable", bluetoothSetting);
|
||||
s.putBoolean(PREF_BT_ENABLE, btSetting);
|
||||
long now = System.currentTimeMillis();
|
||||
settingsManager.mergeSettings(s, "bt");
|
||||
settingsManager.mergeSettings(s, BT_NAMESPACE);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Merging settings took " + duration + " ms");
|
||||
@@ -357,21 +373,21 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
Uri uri = data.getParcelableExtra(EXTRA_RINGTONE_PICKED_URI);
|
||||
if (uri == null) {
|
||||
// The user chose silence
|
||||
s.putBoolean("notifySound", false);
|
||||
s.put("notifyRingtoneName", "");
|
||||
s.put("notifyRingtoneUri", "");
|
||||
s.putBoolean(PREF_NOTIFY_SOUND, false);
|
||||
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
|
||||
s.put(PREF_NOTIFY_RINGTONE_URI, "");
|
||||
} else if (RingtoneManager.isDefault(uri)) {
|
||||
// The user chose the default
|
||||
s.putBoolean("notifySound", true);
|
||||
s.put("notifyRingtoneName", "");
|
||||
s.put("notifyRingtoneUri", "");
|
||||
s.putBoolean(PREF_NOTIFY_SOUND, true);
|
||||
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
|
||||
s.put(PREF_NOTIFY_RINGTONE_URI, "");
|
||||
} else {
|
||||
// The user chose a ringtone other than the default
|
||||
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
|
||||
String name = r.getTitle(getContext());
|
||||
s.putBoolean("notifySound", true);
|
||||
s.put("notifyRingtoneName", name);
|
||||
s.put("notifyRingtoneUri", uri.toString());
|
||||
s.putBoolean(PREF_NOTIFY_SOUND, true);
|
||||
s.put(PREF_NOTIFY_RINGTONE_NAME, name);
|
||||
s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString());
|
||||
}
|
||||
storeSettings(s);
|
||||
}
|
||||
@@ -381,7 +397,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
String namespace = ((SettingsUpdatedEvent) e).getNamespace();
|
||||
if (namespace.equals("bt") || namespace.equals("tor")
|
||||
if (namespace.equals(BT_NAMESPACE)
|
||||
|| namespace.equals(TOR_NAMESPACE)
|
||||
|| namespace.equals(SETTINGS_NAMESPACE)) {
|
||||
LOG.info("Settings updated");
|
||||
loadSettings();
|
||||
|
||||
@@ -30,8 +30,8 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SplashScreenActivity.class.getName());
|
||||
|
||||
// This build expires on 1 March 2017
|
||||
private static final long EXPIRY_DATE = 1488322800 * 1000L;
|
||||
// This build expires on 1 May 2017
|
||||
private static final long EXPIRY_DATE = 1493593200 * 1000L;
|
||||
|
||||
@Inject
|
||||
protected ConfigController configController;
|
||||
@@ -83,6 +83,10 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNewScreenFilterWarning() {
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
if (TESTING) {
|
||||
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
|
||||
|
||||
@@ -9,6 +9,16 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
*/
|
||||
public interface AndroidNotificationManager {
|
||||
|
||||
String PREF_NOTIFY_PRIVATE = "notifyPrivateMessages";
|
||||
String PREF_NOTIFY_GROUP = "notifyGroupMessages";
|
||||
String PREF_NOTIFY_FORUM = "notifyForumPosts";
|
||||
String PREF_NOTIFY_BLOG = "notifyBlogPosts";
|
||||
|
||||
String PREF_NOTIFY_SOUND = "notifySound";
|
||||
String PREF_NOTIFY_RINGTONE_NAME = "notifyRingtoneName";
|
||||
String PREF_NOTIFY_RINGTONE_URI = "notifyRingtoneUri";
|
||||
String PREF_NOTIFY_VIBRATION = "notifyVibration";
|
||||
|
||||
void clearContactNotification(ContactId c);
|
||||
|
||||
void clearAllContactNotifications();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.briar.api.android;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public interface ScreenFilterMonitor {
|
||||
|
||||
@UiThread
|
||||
Set<String> getApps();
|
||||
|
||||
@UiThread
|
||||
void storeAppsAsShown(Collection<String> s, boolean persistent);
|
||||
}
|
||||
@@ -8,15 +8,17 @@ import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.thoughtcrime.securesms.components.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.components.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
@@ -33,17 +35,21 @@ import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
|
||||
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
||||
import static android.graphics.PixelFormat.TRANSLUCENT;
|
||||
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class EmojiProvider {
|
||||
|
||||
private static volatile EmojiProvider INSTANCE = null;
|
||||
|
||||
private static final Paint PAINT =
|
||||
new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
new Paint(FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG);
|
||||
|
||||
@Inject
|
||||
AndroidExecutor androidExecutor;
|
||||
@@ -64,7 +70,7 @@ public class EmojiProvider {
|
||||
private static final int EMOJI_PER_ROW = 32;
|
||||
|
||||
private final Context context;
|
||||
private final float decodeScale, verticalPad;
|
||||
private final float decodeScale;
|
||||
private final List<EmojiPageModel> staticPages;
|
||||
|
||||
static EmojiProvider getInstance(Context context) {
|
||||
@@ -73,8 +79,9 @@ public class EmojiProvider {
|
||||
if (INSTANCE == null) {
|
||||
LOG.info("Creating new instance of EmojiProvider");
|
||||
INSTANCE = new EmojiProvider(context);
|
||||
((BaseActivity) context).getActivityComponent()
|
||||
.inject(INSTANCE);
|
||||
BriarApplication app =
|
||||
(BriarApplication) context.getApplicationContext();
|
||||
app.getApplicationComponent().inject(INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,11 +93,10 @@ public class EmojiProvider {
|
||||
float drawerSize =
|
||||
context.getResources().getDimension(R.dimen.emoji_drawer_size);
|
||||
decodeScale = Math.min(1f, drawerSize / EMOJI_RAW_HEIGHT);
|
||||
verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
||||
staticPages = EmojiPages.getPages(context);
|
||||
for (EmojiPageModel page : staticPages) {
|
||||
if (page.hasSpriteMap()) {
|
||||
final EmojiPageBitmap pageBitmap = new EmojiPageBitmap(page);
|
||||
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(page);
|
||||
for (int i = 0; i < page.getEmoji().length; i++) {
|
||||
offsets.put(Character.codePointAt(page.getEmoji()[i], 0),
|
||||
new DrawInfo(pageBitmap, i));
|
||||
@@ -100,8 +106,8 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Spannable emojify(@Nullable CharSequence text,
|
||||
@NonNull TextView tv) {
|
||||
@UiThread
|
||||
Spannable emojify(@Nullable CharSequence text, TextView tv) {
|
||||
if (text == null) return null;
|
||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
@@ -118,12 +124,13 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@UiThread
|
||||
Drawable getEmojiDrawable(int emojiCode) {
|
||||
return getEmojiDrawable(offsets.get(emojiCode));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Drawable getEmojiDrawable(DrawInfo drawInfo) {
|
||||
private Drawable getEmojiDrawable(@Nullable DrawInfo drawInfo) {
|
||||
if (drawInfo == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -154,10 +161,10 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
|
||||
class EmojiDrawable extends Drawable {
|
||||
static class EmojiDrawable extends Drawable {
|
||||
|
||||
private final DrawInfo info;
|
||||
private final float intrinsicWidth, intrinsicHeight;
|
||||
private final float intrinsicWidth, intrinsicHeight, verticalPad;
|
||||
|
||||
private Bitmap bmp;
|
||||
|
||||
@@ -165,6 +172,7 @@ public class EmojiProvider {
|
||||
this.info = info;
|
||||
intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
||||
intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
||||
verticalPad = EMOJI_VERT_PAD * decodeScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,7 +186,7 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
public void draw(Canvas canvas) {
|
||||
if (bmp == null) {
|
||||
return;
|
||||
}
|
||||
@@ -212,7 +220,7 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
public void setColorFilter(@Nullable ColorFilter cf) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,49 +253,46 @@ public class EmojiProvider {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private ListenableFutureTask<Bitmap> get() {
|
||||
if (bitmapReference != null && bitmapReference.get() != null) {
|
||||
return new ListenableFutureTask<>(bitmapReference.get());
|
||||
} else if (task != null) {
|
||||
return task;
|
||||
} else {
|
||||
Callable<Bitmap> callable = new Callable<Bitmap>() {
|
||||
@Override
|
||||
@Nullable
|
||||
public Bitmap call() throws Exception {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading page " + model.getSprite());
|
||||
return loadPage();
|
||||
} catch (IOException ioe) {
|
||||
LOG.log(WARNING, ioe.toString(), ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
task = null;
|
||||
}
|
||||
}.execute();
|
||||
if (bitmapReference != null) {
|
||||
Bitmap bitmap = bitmapReference.get();
|
||||
if (bitmap != null) return new ListenableFutureTask<>(bitmap);
|
||||
}
|
||||
if (task != null) return task;
|
||||
Callable<Bitmap> callable = new Callable<Bitmap>() {
|
||||
@Override
|
||||
@Nullable
|
||||
public Bitmap call() throws Exception {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading page " + model.getSprite());
|
||||
return loadPage();
|
||||
}
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
task = null;
|
||||
}
|
||||
}.execute();
|
||||
return task;
|
||||
}
|
||||
|
||||
private Bitmap loadPage() throws IOException {
|
||||
if (bitmapReference != null && bitmapReference.get() != null)
|
||||
return bitmapReference.get();
|
||||
if (bitmapReference != null) {
|
||||
Bitmap bitmap = bitmapReference.get();
|
||||
if (bitmap != null) return bitmap;
|
||||
}
|
||||
|
||||
try {
|
||||
final Bitmap bitmap = BitmapUtil.createScaledBitmap(context,
|
||||
Bitmap bitmap = BitmapUtil.createScaledBitmap(context,
|
||||
"file:///android_asset/" + model.getSprite(),
|
||||
decodeScale);
|
||||
bitmapReference = new SoftReference<>(bitmap);
|
||||
|
||||
@@ -2,19 +2,21 @@ package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.controller.DbController;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -23,7 +25,8 @@ import javax.inject.Inject;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -32,27 +35,27 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent";
|
||||
private static final int EMOJI_LRU_SIZE = 50;
|
||||
|
||||
private final LinkedHashSet<String> recentlyUsed;
|
||||
private Settings settings;
|
||||
private final LinkedHashSet<String> recentlyUsed; // UI thread
|
||||
|
||||
@Inject
|
||||
SettingsManager settingsManager;
|
||||
|
||||
@Inject
|
||||
DbController dbController;
|
||||
@DatabaseExecutor
|
||||
Executor dbExecutor;
|
||||
|
||||
RecentEmojiPageModel(Context context) {
|
||||
if (!(context instanceof BaseActivity)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Needs to be created from BaseActivity");
|
||||
}
|
||||
((BaseActivity) context).getActivityComponent().inject(this);
|
||||
BriarApplication app =
|
||||
(BriarApplication) context.getApplicationContext();
|
||||
app.getApplicationComponent().inject(this);
|
||||
recentlyUsed = getPersistedCache();
|
||||
}
|
||||
|
||||
private LinkedHashSet<String> getPersistedCache() {
|
||||
String serialized;
|
||||
try {
|
||||
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
// FIXME: Don't make DB calls on the UI thread
|
||||
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
serialized = settings.get(EMOJI_LRU_PREFERENCE);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
@@ -67,7 +70,6 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
return R.drawable.ic_emoji_recent;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String[] getEmoji() {
|
||||
return toReversePrimitiveArray(recentlyUsed);
|
||||
@@ -92,31 +94,26 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
save(recentlyUsed);
|
||||
save(serialize(recentlyUsed));
|
||||
}
|
||||
|
||||
private String serialize(LinkedHashSet<String> emojis) {
|
||||
String result = "";
|
||||
for (String emoji : emojis) {
|
||||
result += emoji + ";";
|
||||
}
|
||||
if (!emojis.isEmpty())
|
||||
result = result.substring(0, result.length() - 1);
|
||||
return result;
|
||||
return StringUtils.join(emojis, ";");
|
||||
}
|
||||
|
||||
private LinkedHashSet<String> deserialize(@Nullable String str) {
|
||||
String[] list = str == null ? new String[] {} : str.split(";");
|
||||
private LinkedHashSet<String> deserialize(@Nullable String serialized) {
|
||||
if (serialized == null) return new LinkedHashSet<>();
|
||||
String[] list = serialized.split(";");
|
||||
LinkedHashSet<String> result = new LinkedHashSet<>(list.length);
|
||||
Collections.addAll(result, list);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void save(final LinkedHashSet<String> recentlyUsed) {
|
||||
dbController.runOnDbThread(new Runnable() {
|
||||
private void save(final String serialized) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String serialized = serialize(recentlyUsed);
|
||||
Settings settings = new Settings();
|
||||
settings.put(EMOJI_LRU_PREFERENCE, serialized);
|
||||
try {
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
@@ -128,8 +125,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
});
|
||||
}
|
||||
|
||||
private String[] toReversePrimitiveArray(
|
||||
@NonNull LinkedHashSet<String> emojiSet) {
|
||||
private String[] toReversePrimitiveArray(LinkedHashSet<String> emojiSet) {
|
||||
String[] emojis = new String[emojiSet.size()];
|
||||
int i = emojiSet.size() - 1;
|
||||
for (String emoji : emojiSet) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
40
briar-android/src/main/res/layout/alert_dialog_checkbox.xml
Normal file
40
briar-android/src/main/res/layout/alert_dialog_checkbox.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:filterTouchesWhenObscured="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:theme="@style/BriarTheme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alert_dialog_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingRight="6dp"
|
||||
android:text="TextView"
|
||||
android:textAppearance="@style/BriarTextBody"
|
||||
android:theme="@+theme/BriarDialogTheme"/>
|
||||
</ScrollView>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox_screen_filter_reminder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_weight="0"
|
||||
android:filterTouchesWhenObscured="false"
|
||||
android:text="@string/checkbox_dont_show_again"
|
||||
android:textAppearance="@style/BriarTextBody"/>
|
||||
</LinearLayout>
|
||||
@@ -11,7 +11,7 @@
|
||||
android:layout_height="@dimen/blogs_avatar_normal_size"
|
||||
android:layout_alignTop="@+id/authorName"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatarIcon"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
app:civ_border_color="@color/action_bar_text"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/contactStatus"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/nameContact1"
|
||||
@@ -77,7 +77,7 @@
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/nameContact2"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_gravity="bottom|left"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unreadCountView"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/nameView"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/nameView"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
tools:src="@mipmap/ic_launcher_round"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<string name="hide">Ausblenden</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="got_it">Okay, verstanden.</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="accept">Annehmen</string>
|
||||
<string name="decline">Ablehnen</string>
|
||||
@@ -68,9 +69,12 @@
|
||||
<string name="online">Online</string>
|
||||
<string name="offline">Offline</string>
|
||||
<string name="send">Senden</string>
|
||||
<string name="allow">Erlauben</string>
|
||||
<string name="open">Öffnen</string>
|
||||
<string name="no_data">Keine Daten</string>
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">Der eingegebene Text ist leider zu lang</string>
|
||||
<string name="show_onboarding">Hilfe anzeigen</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nVerwende das \"+\"-Icon am oberen Bildschirmrand an und folge den Anweisungen um Freunde zu deiner Kontaktliste hinzuzufügen.\n\nDenke daran dass Du neue Kontakte nur dann hinzufügen kannst, wenn Du ihnen physisch begegnest. Das hindert andere daran, sich als Deine Person auszugeben oder Deine Nachrichten zu lesen.</string>
|
||||
<string name="date_no_private_messages">Keine Nachrichten.</string>
|
||||
@@ -83,6 +87,7 @@
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Kontakt hinzufügen</string>
|
||||
<string name="your_nickname">Wähle die zu verwendende Identität:</string>
|
||||
<string name="face_to_face">Um einen neuen Kontakt hinzufügen zu können, ist eine reale Begegnung erforderlich.\n\nDadurch wird eine betrügerische Impersonation und der unautorisierte Zugriff auf die Kommunikation verhindert.</string>
|
||||
<string name="continue_button">Weiter</string>
|
||||
<string name="your_invitation_code">Dein Einladungs-Code ist</string>
|
||||
<string name="enter_invitation_code">Bitte gib den Einladungs-Code Deines Kontakts an:</string>
|
||||
@@ -95,6 +100,7 @@
|
||||
<string name="your_confirmation_code">Dein Bestätigungscode ist</string>
|
||||
<string name="enter_confirmation_code">Bitte gebe den Bestätigungscode Deines Kontakts ein:</string>
|
||||
<string name="waiting_for_contact">Warte auf Kontakt\u2026</string>
|
||||
<string name="waiting_for_contact_to_scan">Warte auf Scan und Verbindung mit dem Kontakt\u2026</string>
|
||||
<string name="exchanging_contact_details">Kontaktdetails werden ausgetauscht\u2026</string>
|
||||
<string name="codes_do_not_match">Codes stimmen nicht überein</string>
|
||||
<string name="interfering">Möglicherweise stört jemand Deine Verbindung</string>
|
||||
@@ -144,21 +150,38 @@
|
||||
<string name="groups_create_group_button">Gruppe erstellen</string>
|
||||
<string name="groups_create_group_invitation_button">Einladung schicken</string>
|
||||
<string name="groups_create_group_hint">Gebe Deiner privaten Gruppe einen Namen</string>
|
||||
<string name="groups_invitation_sent">Gruppeneinladung versendet</string>
|
||||
<string name="groups_compose_message">Nachricht erstellen</string>
|
||||
<string name="groups_message_sent">Nachricht gesendet</string>
|
||||
<string name="groups_member_list">Mitglieder</string>
|
||||
<string name="groups_invite_members">Mitglieder einladen</string>
|
||||
<string name="groups_member_created_you">Du hast diese Gruppe erstellt</string>
|
||||
<string name="groups_member_created">%s hat diese Gruppe erstellt</string>
|
||||
<string name="groups_member_joined_you">Du bist der Gruppe beigetreten</string>
|
||||
<string name="groups_member_joined">%s ist der Gruppe beigetreten</string>
|
||||
<string name="groups_leave">Gruppe verlassen</string>
|
||||
<string name="groups_leave_dialog_title">Verlassen der Gruppe bestätigen</string>
|
||||
<string name="groups_leave_dialog_message">Bist Du sicher, dass Du die Gruppe verlassen willst?</string>
|
||||
<string name="groups_dissolve">Gruppe auflösen</string>
|
||||
<string name="groups_dissolve_dialog_title">Auflösung der Gruppe bestätigen</string>
|
||||
<string name="groups_dissolve_dialog_message">Willst Du wirklich diese Gruppe auflösen?\n\nAlle anderen Teilnehmer verlieren die Möglichkeit der Kommunikation ohne möglicherweise die neuesten Nachrichten erhalten zu haben.</string>
|
||||
<string name="groups_dissolve_button">Auflösen</string>
|
||||
<string name="groups_dissolved_dialog_title">Gruppe wurde aufgelöst</string>
|
||||
<string name="groups_dissolved_dialog_message">Der Ersteller dieser Gruppe hat dieselbe aufgelöst.\n\nEs können keine weiteren Nachrichten mehr in dieser Gruppe geschrieben werden und möglicherweise wurden noch nicht alle Nachrichten empfangen.</string>
|
||||
<!--Private Group Invitations-->
|
||||
<string name="groups_invitations_title">Gruppeneinladungen</string>
|
||||
<string name="groups_invitations_invitation_sent">%1$s wurde in die Gruppe \"%2$s\" eingeladen.</string>
|
||||
<string name="groups_invitations_invitation_received">%1$s hat dich eingeladen der Gruppe \"%2$s\" beizutreten.</string>
|
||||
<string name="groups_invitations_joined">Gruppe beigetreten</string>
|
||||
<string name="groups_invitations_declined">Gruppeneinladung abgelehnt</string>
|
||||
<string name="groups_invitations_response_accepted_sent">Gruppeneinladung von %s angenommen.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Gruppeneinladung von %s abgelehnt.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s hat die Einladung zur Gruppe angenommen.</string>
|
||||
<string name="groups_invitations_response_declined_received">%s hat die Einladung zur Gruppe abgelehnt.</string>
|
||||
<!--Private Groups Revealing Contacts-->
|
||||
<string name="groups_reveal_contacts">Kontakte teilen</string>
|
||||
<string name="groups_reveal_dialog_message">Kontakte können mit allen derzeitigen und zukünftigen Mitgliedern dieser Gruppe geteilt werden.\n\nDas beschleunigt die Verbindung zu der Gruppe und macht sie zusätzlich zuverlässiger, da Kommunikation mit den Mitgliedern auch dann erfolgen kann, wenn der Ersteller der Gruppe offline ist.</string>
|
||||
<string name="groups_reveal_visible">Verbindung zum Kontakt ist für die Gruppe sichtbar</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Du hast noch keine Foren.\n\nWarum erstellst du nicht einfach selbst ein neues Forum, indem du auf das \"+\"-Icon am oberen Bildschirmrand tippst?\n\nDu kannst auch deine Kontakte auffordern, Foren mit dir zu teilen.</string>
|
||||
<string name="create_forum_title">Neues Forum</string>
|
||||
@@ -259,9 +282,6 @@
|
||||
<string name="bluetooth_setting">Über Bluetooth verbinden</string>
|
||||
<string name="bluetooth_setting_enabled">Sobald Kontakte in der Nähe sind</string>
|
||||
<string name="bluetooth_setting_disabled">Nur beim Hinzufügen von Kontakten</string>
|
||||
<string name="tor_mobile_setting">Über Tor verbinden</string>
|
||||
<string name="tor_mobile_setting_enabled">Wenn WLAN oder Mobildaten verwendet werden</string>
|
||||
<string name="tor_mobile_setting_disabled">Nur wenn über WLAN verbunden</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Sicherheit</string>
|
||||
<string name="change_password">Passwort ändern</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user