mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
123 Commits
beta-0.16.
...
299-blueto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e9153bd81 | ||
|
|
15fa44c1b1 | ||
|
|
7ffe1b8bae | ||
|
|
bb8713ddcb | ||
|
|
1e9afd14db | ||
|
|
86ea6eae63 | ||
|
|
56ba639084 | ||
|
|
8c16c2107f | ||
|
|
cefe2b09e0 | ||
|
|
e6b1597fa7 | ||
|
|
8937d3cd9c | ||
|
|
51f320d147 | ||
|
|
e402a894bb | ||
|
|
9b577f1219 | ||
|
|
220f678403 | ||
|
|
4173fc4daa | ||
|
|
c6756d2145 | ||
|
|
6731f6eeb5 | ||
|
|
6f7f8b40e3 | ||
|
|
1a83b2c99b | ||
|
|
f641fae1c7 | ||
|
|
deb43d9872 | ||
|
|
cee4e1305e | ||
|
|
a1f989c43c | ||
|
|
b67abadbac | ||
|
|
8c29c85696 | ||
|
|
4fe4c298d7 | ||
|
|
13d35229d5 | ||
|
|
f0137b41b6 | ||
|
|
b221d21903 | ||
|
|
8bac202626 | ||
|
|
973151c949 | ||
|
|
ed26ab78a5 | ||
|
|
8454b2d235 | ||
|
|
91d0f89f60 | ||
|
|
e074672e86 | ||
|
|
6c1901fe5b | ||
|
|
49052be627 | ||
|
|
5b5b540630 | ||
|
|
9993bac3a1 | ||
|
|
3c95988693 | ||
|
|
fc5c3b470e | ||
|
|
53f05a72ba | ||
|
|
2c10ae7d06 | ||
|
|
6b9010c557 | ||
|
|
1bf0fdfa81 | ||
|
|
237759aac0 | ||
|
|
2a141e0a97 | ||
|
|
d6900be68e | ||
|
|
a35d7c7204 | ||
|
|
86287f9241 | ||
|
|
0b2e3dd96f | ||
|
|
90aa1d1ce7 | ||
|
|
ef2286ab53 | ||
|
|
47b25f3221 | ||
|
|
c30bfa12ce | ||
|
|
d0fc04251d | ||
|
|
dcbb41eb7a | ||
|
|
5c51259269 | ||
|
|
7eefa07052 | ||
|
|
999bdf8866 | ||
|
|
911c0c0fd9 | ||
|
|
99d8cc64a6 | ||
|
|
ba727d7568 | ||
|
|
ed01048f9f | ||
|
|
043ee3c58e | ||
|
|
6e0af7deda | ||
|
|
9591db2097 | ||
|
|
329a4c64f6 | ||
|
|
79015bc5ae | ||
|
|
27422ab9f9 | ||
|
|
abcb682498 | ||
|
|
5044127c46 | ||
|
|
0e4b8ca62e | ||
|
|
822017c69c | ||
|
|
eb6561b93d | ||
|
|
eb9d0c00a8 | ||
|
|
d24b1884a2 | ||
|
|
078534889e | ||
|
|
e92713006a | ||
|
|
18f43f3bc1 | ||
|
|
a4118b40e1 | ||
|
|
de29fbc324 | ||
|
|
3197dcf9b5 | ||
|
|
35aad409fd | ||
|
|
08ce6a7331 | ||
|
|
33a0099065 | ||
|
|
34d20fafda | ||
|
|
aafddcd0f0 | ||
|
|
0d6983b4ef | ||
|
|
69bfb72171 | ||
|
|
1aa33ec9b2 | ||
|
|
6702df1e22 | ||
|
|
c1748c9a86 | ||
|
|
9df624c62a | ||
|
|
0ee6197d7f | ||
|
|
b03a7dce3e | ||
|
|
6c59d7dd5f | ||
|
|
050191f0ef | ||
|
|
4b5a19ce5d | ||
|
|
7c4dd991b9 | ||
|
|
8455569e88 | ||
|
|
d25676559c | ||
|
|
a9437f7985 | ||
|
|
8141a97fc9 | ||
|
|
db842bd7e4 | ||
|
|
6dbec3a864 | ||
|
|
29f658cf4d | ||
|
|
ca83744a84 | ||
|
|
d91a9e2be4 | ||
|
|
8408c3f467 | ||
|
|
544c83a64c | ||
|
|
3800cd5e4f | ||
|
|
259f2cd419 | ||
|
|
20eb022c36 | ||
|
|
531e555b52 | ||
|
|
a9024aa34b | ||
|
|
d4e3b7842c | ||
|
|
167fddfbcc | ||
|
|
a48d642648 | ||
|
|
9a70f054c7 | ||
|
|
ca43d13bd6 | ||
|
|
5b71004179 |
@@ -7,8 +7,7 @@ cache:
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
|
||||
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
|
||||
- echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;23.0.3"
|
||||
|
||||
test:
|
||||
script:
|
||||
|
||||
@@ -7,13 +7,13 @@ apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
buildToolsVersion '25.0.0'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 14
|
||||
versionName "0.14"
|
||||
versionCode 1611
|
||||
versionName "0.16.11"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-core')
|
||||
compile project(path: ':bramble-core', configuration: 'default')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
}
|
||||
@@ -32,31 +32,31 @@ dependencies {
|
||||
def torBinaryDir = 'src/main/res/raw'
|
||||
|
||||
task downloadTorGeoIp(type: Download) {
|
||||
src 'https://briarproject.org/build/geoip-2017-05-02.zip'
|
||||
src 'https://briarproject.org/build/geoip-2017-09-06.zip'
|
||||
dest "$torBinaryDir/geoip.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArm(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm.zip'
|
||||
dest "$torBinaryDir/tor_arm.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryArmPie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-arm-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm-pie.zip'
|
||||
dest "$torBinaryDir/tor_arm_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86.zip'
|
||||
dest "$torBinaryDir/tor_x86.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86Pie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.11-x86-pie.zip'
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86-pie.zip'
|
||||
dest "$torBinaryDir/tor_x86_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
@@ -64,31 +64,31 @@ task downloadTorBinaryX86Pie(type: Download) {
|
||||
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
|
||||
src "$torBinaryDir/geoip.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '51f4d1272fb867e1f3b36b67a584e2a33c40b40f62305457d799fd399cd77c9b'
|
||||
checksum 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
||||
src "$torBinaryDir/tor_arm.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '1da6008663a8ad98b349e62acbbf42c379f65ec504fa467cb119c187cd5a4c6b'
|
||||
checksum '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
||||
src "$torBinaryDir/tor_arm_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'eb061f880829e05f104690ac744848133f2dacef04759d425a2cff0df32c271e'
|
||||
checksum '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
||||
src "$torBinaryDir/tor_x86.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'f5308aff8303daca082f82227d02b51ddedba4ab1d1420739ada0427ae5dbb41'
|
||||
checksum '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
||||
src "$torBinaryDir/tor_x86_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '889a6c81ac73d05d35ed610ca5a913cee44d333e4ae1749c2a107f2f7dd8197b'
|
||||
checksum '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
||||
@@ -39,7 +39,7 @@ public class AndroidPluginModule {
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, random, backoffFactory);
|
||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||
locationUtils, reporter, eventBus, torSocketFactory,
|
||||
backoffFactory);
|
||||
|
||||
@@ -8,21 +8,26 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
@@ -30,23 +35,15 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -61,8 +58,6 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
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;
|
||||
@@ -75,14 +70,10 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class DroidtoothPlugin implements DuplexPlugin {
|
||||
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
private static final String FOUND =
|
||||
"android.bluetooth.device.action.FOUND";
|
||||
private static final String DISCOVERY_FINISHED =
|
||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
@@ -95,6 +86,8 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile ArrayList<BluetoothEnableDisableReason>
|
||||
enableDisableReasons = new ArrayList<>();
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
private volatile BluetoothServerSocket socket = null;
|
||||
|
||||
@@ -165,10 +158,8 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
bind();
|
||||
} else {
|
||||
// Enable Bluetooth if settings allow
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
||||
wasEnabledByUs = true;
|
||||
if (adapter.enable()) LOG.info("Enabling Bluetooth");
|
||||
else LOG.info("Could not enable Bluetooth");
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, true)) {
|
||||
enableAdapter(BluetoothEnableDisableReason.COMMUNICATION);
|
||||
} else {
|
||||
LOG.info("Not enabling Bluetooth");
|
||||
}
|
||||
@@ -259,13 +250,42 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private void enableAdapter(BluetoothEnableDisableReason reason) {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
|
||||
if(!enableDisableReasons.contains(reason)) {
|
||||
enableDisableReasons.add(reason);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
// Disable Bluetooth if we enabled it and it's still enabled
|
||||
if (wasEnabledByUs && adapter.isEnabled()) {
|
||||
disableAdapter(true);
|
||||
}
|
||||
|
||||
private void disableAdapter(boolean force){
|
||||
disableAdapter(null, force);
|
||||
}
|
||||
|
||||
private void disableAdapter(BluetoothEnableDisableReason reason,
|
||||
boolean force) {
|
||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs
|
||||
&& (enableDisableReasons.contains(reason) || force)) {
|
||||
|
||||
if(enableDisableReasons.contains(reason)){
|
||||
enableDisableReasons.remove(reason);
|
||||
}
|
||||
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
}
|
||||
@@ -363,8 +383,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
@@ -374,90 +393,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
if (!isRunning()) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
UUID uuid = UUID.nameUUIDFromBytes(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
CompletionService<BluetoothSocket> complete =
|
||||
new ExecutorCompletionService<>(ioExecutor);
|
||||
List<Future<BluetoothSocket>> futures = new ArrayList<>();
|
||||
if (alice) {
|
||||
// Return the first connected socket
|
||||
futures.add(complete.submit(new ListeningTask(ss)));
|
||||
futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
|
||||
} else {
|
||||
// Return the first socket with readable data
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new ListeningTask(ss))));
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new DiscoveryTask(uuid.toString()))));
|
||||
}
|
||||
BluetoothSocket chosen = null;
|
||||
try {
|
||||
Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
|
||||
if (f == null) return null; // No task completed within the timeout
|
||||
chosen = f.get();
|
||||
return new DroidtoothTransportConnection(this, chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSockets(final List<Future<BluetoothSocket>> futures,
|
||||
@Nullable final BluetoothSocket chosen) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<BluetoothSocket> f : futures) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else {
|
||||
BluetoothSocket s = f.get();
|
||||
if (s != null && s != chosen) {
|
||||
LOG.info("Closing unwanted socket");
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
return;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -472,7 +407,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
@@ -513,6 +448,35 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
EnableBluetoothEvent enable = (EnableBluetoothEvent) e;
|
||||
enableAdapterAsync(enable.getReason());
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
DisableBluetoothEvent disable = (DisableBluetoothEvent) e;
|
||||
disableAdapterAsync(disable.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAdapterAsync(final BluetoothEnableDisableReason reason) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
enableAdapter(reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void disableAdapterAsync(final BluetoothEnableDisableReason reason) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
disableAdapter(reason, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@@ -536,115 +500,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final String uuid;
|
||||
|
||||
private DiscoveryTask(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
// Repeat discovery until we connect or get interrupted
|
||||
while (true) {
|
||||
// Discover nearby devices
|
||||
LOG.info("Discovering nearby devices");
|
||||
List<String> addresses = discoverDevices();
|
||||
if (addresses.isEmpty()) {
|
||||
LOG.info("No devices discovered");
|
||||
continue;
|
||||
}
|
||||
// Connect to any device with the right UUID
|
||||
for (String address : addresses) {
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
LOG.info("Outgoing connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> discoverDevices() throws InterruptedException {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(FOUND);
|
||||
filter.addAction(DISCOVERY_FINISHED);
|
||||
DiscoveryReceiver disco = new DiscoveryReceiver();
|
||||
appContext.registerReceiver(disco, filter);
|
||||
LOG.info("Starting discovery");
|
||||
adapter.startDiscovery();
|
||||
return disco.waitForAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiscoveryReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final List<String> addresses = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(DISCOVERY_FINISHED)) {
|
||||
LOG.info("Discovery finished");
|
||||
ctx.unregisterReceiver(this);
|
||||
finished.countDown();
|
||||
} else if (action.equals(FOUND)) {
|
||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Discovered device: " +
|
||||
scrubMacAddress(d.getAddress()));
|
||||
}
|
||||
addresses.add(d.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> waitForAddresses() throws InterruptedException {
|
||||
finished.await();
|
||||
List<String> shuffled = new ArrayList<>(addresses);
|
||||
Collections.shuffle(shuffled);
|
||||
return shuffled;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListeningTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final BluetoothServerSocket serverSocket;
|
||||
|
||||
private ListeningTask(BluetoothServerSocket serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws IOException {
|
||||
BluetoothSocket s = serverSocket.accept();
|
||||
LOG.info("Incoming connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final Callable<BluetoothSocket> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<BluetoothSocket> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
BluetoothSocket s = connectionTask.call();
|
||||
InputStream in = s.getInputStream();
|
||||
while (in.available() == 0) {
|
||||
LOG.info("Waiting for data");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
LOG.info("Data available");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final BluetoothServerSocket ss;
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
@@ -31,15 +32,18 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final SecureRandom secureRandom;
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SecureRandom secureRandom, BackoffFactory backoffFactory) {
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
this.secureRandom = secureRandom;
|
||||
this.eventBus = eventBus;
|
||||
this.backoffFactory = backoffFactory;
|
||||
}
|
||||
|
||||
@@ -57,7 +61,10 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
@@ -56,6 +55,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -85,13 +85,13 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
|
||||
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.api.plugin.TorConstants.PROP_ONION;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
private static final String PROP_ONION = "onion";
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
|
||||
};
|
||||
@@ -149,7 +149,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||
Object o = appContext.getSystemService(POWER_SERVICE);
|
||||
PowerManager pm = (PowerManager) o;
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin");
|
||||
// This tag will prevent Huawei's powermanager from killing us.
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
@@ -539,16 +540,21 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
// TODO: Pass properties to connectAndCallBack()
|
||||
for (ContactId c : callback.getRemoteProperties().keySet())
|
||||
if (!connected.contains(c)) connectAndCallBack(c);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(final ContactId c) {
|
||||
private void connectAndCallBack(final ContactId c,
|
||||
final TransportProperties p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DuplexTransportConnection d = createConnection(c);
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
@@ -560,8 +566,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
return createConnection(callback.getRemoteProperties(c));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
String onion = p.get(PROP_ONION);
|
||||
if (StringUtils.isNullOrEmpty(onion)) return null;
|
||||
if (!ONION.matcher(onion).matches()) {
|
||||
@@ -589,17 +598,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -21,12 +22,12 @@ class TorTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
return IoUtils.getInputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
return IoUtils.getOutputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,8 +10,6 @@ public interface CryptoComponent {
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
PseudoRandom getPseudoRandom(int seed1, int seed2);
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
|
||||
KeyPair generateAgreementKeyPair();
|
||||
@@ -24,15 +22,6 @@ public interface CryptoComponent {
|
||||
|
||||
KeyParser getMessageKeyParser();
|
||||
|
||||
/** Generates a random invitation code. */
|
||||
int generateBTInvitationCode();
|
||||
|
||||
/**
|
||||
* Derives a confirmation code from the given master secret.
|
||||
* @param alice whether the code is for use by Alice or Bob.
|
||||
*/
|
||||
int deriveBTConfirmationCode(SecretKey master, boolean alice);
|
||||
|
||||
/**
|
||||
* Derives a stream header key from the given master secret.
|
||||
* @param alice whether the key is for use by Alice or Bob.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A deterministic pseudo-random number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface PseudoRandom {
|
||||
|
||||
byte[] nextBytes(int bytes);
|
||||
}
|
||||
@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
|
||||
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
||||
* Creates a {@link StreamDecrypter} for decrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
|
||||
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
||||
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
/**
|
||||
* The connection timeout in milliseconds.
|
||||
*/
|
||||
long CONNECTION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The confirmation timeout in milliseconds.
|
||||
*/
|
||||
long CONFIRMATION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The number of bits in an invitation or confirmation code. Codes must fit
|
||||
* into six decimal digits.
|
||||
*/
|
||||
int CODE_BITS = 19;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
/**
|
||||
* An interface for receiving updates about the state of an
|
||||
* {@link InvitationTask}.
|
||||
*/
|
||||
public interface InvitationListener {
|
||||
|
||||
/** Called if a connection to the remote peer is established. */
|
||||
void connectionSucceeded();
|
||||
|
||||
/**
|
||||
* Called if a connection to the remote peer cannot be established. This
|
||||
* indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void connectionFailed();
|
||||
|
||||
/** Called if key agreement with the remote peer succeeds. */
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode);
|
||||
|
||||
/**
|
||||
* Called if key agreement with the remote peer fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void keyAgreementFailed();
|
||||
|
||||
/** Called if the remote peer's confirmation check succeeds. */
|
||||
void remoteConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Called if remote peer's confirmation check fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void remoteConfirmationFailed();
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms succeeds. This indicates that the
|
||||
* protocol has ended successfully.
|
||||
*/
|
||||
void pseudonymExchangeSucceeded(String remoteName);
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms fails or the connection is lost.
|
||||
* This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void pseudonymExchangeFailed();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A snapshot of the state of an {@link InvitationTask}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class InvitationState {
|
||||
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final int localConfirmationCode, remoteConfirmationCode;
|
||||
private final boolean connected, connectionFailed;
|
||||
private final boolean localCompared, remoteCompared;
|
||||
private final boolean localMatched, remoteMatched;
|
||||
@Nullable
|
||||
private final String contactName;
|
||||
|
||||
public InvitationState(int localInvitationCode, int remoteInvitationCode,
|
||||
int localConfirmationCode, int remoteConfirmationCode,
|
||||
boolean connected, boolean connectionFailed, boolean localCompared,
|
||||
boolean remoteCompared, boolean localMatched,
|
||||
boolean remoteMatched, @Nullable String contactName) {
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
this.localConfirmationCode = localConfirmationCode;
|
||||
this.remoteConfirmationCode = remoteConfirmationCode;
|
||||
this.connected = connected;
|
||||
this.connectionFailed = connectionFailed;
|
||||
this.localCompared = localCompared;
|
||||
this.remoteCompared = remoteCompared;
|
||||
this.localMatched = localMatched;
|
||||
this.remoteMatched = remoteMatched;
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public int getLocalInvitationCode() {
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
public int getRemoteInvitationCode() {
|
||||
return remoteInvitationCode;
|
||||
}
|
||||
|
||||
public int getLocalConfirmationCode() {
|
||||
return localConfirmationCode;
|
||||
}
|
||||
|
||||
public int getRemoteConfirmationCode() {
|
||||
return remoteConfirmationCode;
|
||||
}
|
||||
|
||||
public boolean getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
public boolean getConnectionFailed() {
|
||||
return connectionFailed;
|
||||
}
|
||||
|
||||
public boolean getLocalCompared() {
|
||||
return localCompared;
|
||||
}
|
||||
|
||||
public boolean getRemoteCompared() {
|
||||
return remoteCompared;
|
||||
}
|
||||
|
||||
public boolean getLocalMatched() {
|
||||
return localMatched;
|
||||
}
|
||||
|
||||
public boolean getRemoteMatched() {
|
||||
return remoteMatched;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getContactName() {
|
||||
return contactName;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A task for exchanging invitations with a remote peer.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTask {
|
||||
|
||||
/**
|
||||
* Adds a listener to be informed of state changes and returns the
|
||||
* task's current state.
|
||||
*/
|
||||
InvitationState addListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Removes the given listener.
|
||||
*/
|
||||
void removeListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Asynchronously starts the connection process.
|
||||
*/
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes matched.
|
||||
*/
|
||||
void localConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes did not match.
|
||||
*/
|
||||
void localConfirmationFailed();
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Creates tasks for exchanging invitations with remote peers.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTaskFactory {
|
||||
|
||||
/**
|
||||
* Creates a task using the given local and remote invitation codes.
|
||||
*/
|
||||
InvitationTask createTask(int localCode, int remoteCode);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
public enum BluetoothEnableDisableReason {
|
||||
COMMUNICATION,
|
||||
ADD_CONTACT
|
||||
}
|
||||
@@ -4,5 +4,10 @@ public interface LanTcpConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.lan");
|
||||
|
||||
// a transport property (shared with contacts)
|
||||
String PROP_IP_PORTS = "ipPorts";
|
||||
|
||||
// a local setting
|
||||
String PREF_LAN_IP_PORTS = "ipPorts";
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public interface PluginCallback {
|
||||
*/
|
||||
Map<ContactId, TransportProperties> getRemoteProperties();
|
||||
|
||||
/**
|
||||
* Returns the plugin's remote transport properties for the given contact.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(ContactId c);
|
||||
|
||||
/**
|
||||
* Merges the given settings with the namespaced settings
|
||||
*/
|
||||
|
||||
@@ -32,11 +32,6 @@ public interface PluginManager {
|
||||
*/
|
||||
Collection<DuplexPlugin> getDuplexPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support invitations.
|
||||
*/
|
||||
Collection<DuplexPlugin> getInvitationPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support key agreement.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,8 @@ public interface TorConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||
|
||||
String PROP_ONION = "onion";
|
||||
|
||||
int SOCKS_PORT = 59050;
|
||||
int CONTROL_PORT = 59051;
|
||||
|
||||
@@ -16,4 +18,5 @@ public interface TorConstants {
|
||||
int PREF_TOR_NETWORK_NEVER = 0;
|
||||
int PREF_TOR_NETWORK_WIFI = 1;
|
||||
int PREF_TOR_NETWORK_ALWAYS = 2;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin.duplex;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
|
||||
@Nullable
|
||||
DuplexTransportConnection createConnection(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports exchanging invitations.
|
||||
*/
|
||||
boolean supportsInvitations();
|
||||
|
||||
/**
|
||||
* Attempts to create and return an invitation connection to the remote
|
||||
* peer. Returns null if no connection can be established within the given
|
||||
* time.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports short-range key agreement.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
abstract class BluetoothEvent extends Event {
|
||||
private BluetoothEnableDisableReason selectedReason;
|
||||
|
||||
BluetoothEvent(BluetoothEnableDisableReason reason){
|
||||
selectedReason = reason;
|
||||
}
|
||||
|
||||
public BluetoothEnableDisableReason getReason(){
|
||||
return selectedReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that asks the Bluetooth plugin to disable the Bluetooth adapter if
|
||||
* we previously enabled it.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class DisableBluetoothEvent extends BluetoothEvent {
|
||||
public DisableBluetoothEvent(BluetoothEnableDisableReason reason) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class EnableBluetoothEvent extends BluetoothEvent {
|
||||
public EnableBluetoothEvent(BluetoothEnableDisableReason reason){
|
||||
super(reason);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,13 @@ public interface TransportPropertyManager {
|
||||
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the remote transport properties for the given contact and
|
||||
* transport.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given properties with the existing local properties for the
|
||||
* given transport.
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
|
||||
InputStream createStreamReader(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream InputStream} for reading from an
|
||||
* invitation stream.
|
||||
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||
* exchangestream.
|
||||
*/
|
||||
InputStream createInvitationStreamReader(InputStream in,
|
||||
InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
|
||||
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link OutputStream OutputStream} for writing to an
|
||||
* invitation stream.
|
||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
||||
* exchange stream.
|
||||
*/
|
||||
OutputStream createInvitationStreamWriter(OutputStream out,
|
||||
OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -59,4 +60,24 @@ public class IoUtils {
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static InputStream getInputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getInputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static OutputStream getOutputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getOutputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -27,6 +28,7 @@ public class StringUtils {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static boolean isNullOrEmpty(@Nullable String s) {
|
||||
return s == null || s.length() == 0;
|
||||
@@ -139,4 +141,12 @@ public class StringUtils {
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
c[i] = (char) ('a' + random.nextInt(26));
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,13 +34,6 @@ public class TestUtils {
|
||||
return getRandomBytes(UniqueId.LENGTH);
|
||||
}
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
c[i] = (char) ('a' + random.nextInt(26));
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
public static SecretKey getSecretKey() {
|
||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ targetCompatibility = 1.6
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-api')
|
||||
compile 'com.madgag.spongycastle:core:1.56.0.0'
|
||||
compile project(path: ':bramble-api', configuration: 'default')
|
||||
compile 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
compile 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
compile 'org.bitlet:weupnp:0.1.4'
|
||||
|
||||
@@ -20,7 +20,7 @@ dependencies {
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.madgag.spongycastle:core:5e791b0eaa9e0c4594231b44f616a52adddb7dccedeb0ad9ad74887e19499a23',
|
||||
'com.madgag.spongycastle:core:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.invitation.InvitationModule;
|
||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
@@ -32,7 +31,6 @@ import dagger.Module;
|
||||
DatabaseExecutorModule.class,
|
||||
EventModule.class,
|
||||
IdentityModule.class,
|
||||
InvitationModule.class,
|
||||
KeyAgreementModule.class,
|
||||
LifecycleModule.class,
|
||||
PluginModule.class,
|
||||
@@ -54,6 +52,7 @@ public class BrambleCoreModule {
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
c.inject(new SyncModule.EagerSingletons());
|
||||
c.inject(new SystemModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
|
||||
@@ -80,7 +80,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
private volatile boolean alice;
|
||||
|
||||
@Inject
|
||||
public ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||
ConnectionManager connectionManager, ContactManager contactManager,
|
||||
@@ -146,12 +146,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(in,
|
||||
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||
alice ? bobHeaderKey : aliceHeaderKey);
|
||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
||||
// Create the writers
|
||||
OutputStream streamWriter =
|
||||
streamWriterFactory.createInvitationStreamWriter(out,
|
||||
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||
alice ? aliceHeaderKey : bobHeaderKey);
|
||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
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;
|
||||
@@ -41,7 +40,6 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_BITS;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||
@@ -68,9 +66,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return s.getBytes(Charset.forName("US-ASCII"));
|
||||
}
|
||||
|
||||
// KDF labels for bluetooth confirmation code derivation
|
||||
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
|
||||
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
|
||||
// KDF labels for contact exchange stream header key derivation
|
||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||
@@ -171,14 +166,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoRandom getPseudoRandom(int seed1, int 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
|
||||
public SecureRandom getSecureRandom() {
|
||||
return secureRandom;
|
||||
@@ -250,20 +237,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return messageEncrypter.getKeyParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateBTInvitationCode() {
|
||||
int codeBytes = (CODE_BITS + 7) / 8;
|
||||
byte[] random = new byte[codeBytes];
|
||||
secureRandom.nextBytes(random);
|
||||
return ByteUtils.readUint(random, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deriveBTConfirmationCode(SecretKey master, boolean alice) {
|
||||
byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM);
|
||||
return ByteUtils.readUint(b, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveHeaderKey(SecretKey master,
|
||||
boolean alice) {
|
||||
|
||||
@@ -32,7 +32,7 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
public StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
||||
OutputStream out, SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Alice in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class AliceConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AliceConnector.class.getName());
|
||||
|
||||
AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(true);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
sendPublicKeyHash(w);
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
sendPublicKey(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
master = deriveMasterSecret(hash, key, true);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(aliceCode, bobCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), true);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Bob in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class BobConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BobConnector.class.getName());
|
||||
|
||||
BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(false);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
sendPublicKeyHash(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
sendPublicKey(w);
|
||||
master = deriveMasterSecret(hash, key, false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(bobCode, aliceCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), false);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
// FIXME: This class has way too many dependencies
|
||||
@NotNullByDefault
|
||||
abstract class Connector extends Thread {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Connector.class.getName());
|
||||
private static final String LABEL_PUBLIC_KEY =
|
||||
"org.briarproject.bramble.invitation.PUBLIC_KEY";
|
||||
|
||||
protected final CryptoComponent crypto;
|
||||
protected final BdfReaderFactory bdfReaderFactory;
|
||||
protected final BdfWriterFactory bdfWriterFactory;
|
||||
protected final ContactExchangeTask contactExchangeTask;
|
||||
protected final ConnectorGroup group;
|
||||
protected final DuplexPlugin plugin;
|
||||
protected final LocalAuthor localAuthor;
|
||||
protected final PseudoRandom random;
|
||||
protected final String pluginName;
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final KeyParser keyParser;
|
||||
|
||||
Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super("Connector");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.group = group;
|
||||
this.plugin = plugin;
|
||||
this.localAuthor = localAuthor;
|
||||
this.random = random;
|
||||
pluginName = plugin.getClass().getName();
|
||||
keyPair = crypto.generateAgreementKeyPair();
|
||||
keyParser = crypto.getAgreementKeyParser();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(boolean alice) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " creating invitation connection");
|
||||
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
|
||||
alice);
|
||||
}
|
||||
|
||||
void sendPublicKeyHash(BdfWriter w) throws IOException {
|
||||
byte[] hash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
w.writeRaw(hash);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
}
|
||||
|
||||
byte[] receivePublicKeyHash(BdfReader r) throws IOException {
|
||||
int hashLength = crypto.getHashLength();
|
||||
byte[] b = r.readRaw(hashLength);
|
||||
if (b.length < hashLength) throw new FormatException();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
return b;
|
||||
}
|
||||
|
||||
void sendPublicKey(BdfWriter w) throws IOException {
|
||||
byte[] key = keyPair.getPublic().getEncoded();
|
||||
w.writeRaw(key);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
|
||||
}
|
||||
|
||||
byte[] receivePublicKey(BdfReader r)
|
||||
throws GeneralSecurityException, IOException {
|
||||
byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||
keyParser.parsePublicKey(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
|
||||
return b;
|
||||
}
|
||||
|
||||
SecretKey deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
|
||||
throws GeneralSecurityException {
|
||||
// Check that the hash matches the key
|
||||
byte[] keyHash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
if (!Arrays.equals(hash, keyHash)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " hash does not match key");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
// Derive the master secret
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " deriving master secret");
|
||||
return crypto.deriveMasterSecret(key, keyPair, alice);
|
||||
}
|
||||
|
||||
void sendConfirmation(BdfWriter w, boolean confirmed) throws IOException {
|
||||
w.writeBoolean(confirmed);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sent confirmation: " + confirmed);
|
||||
}
|
||||
|
||||
boolean receiveConfirmation(BdfReader r) throws IOException {
|
||||
boolean confirmed = r.readBoolean();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " received confirmation: " + confirmed);
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
protected void tryToClose(DuplexTransportConnection conn,
|
||||
boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
||||
|
||||
/**
|
||||
* A task consisting of one or more parallel connection attempts.
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class ConnectorGroup extends Thread implements InvitationTask,
|
||||
ContactExchangeListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectorGroup.class.getName());
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final Collection<InvitationListener> listeners;
|
||||
private final AtomicBoolean connected;
|
||||
private final CountDownLatch localConfirmationLatch;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
private String remoteName = null;
|
||||
|
||||
ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager,
|
||||
int localInvitationCode, int remoteInvitationCode) {
|
||||
super("ConnectorGroup");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
listeners = new CopyOnWriteArrayList<InvitationListener>();
|
||||
connected = new AtomicBoolean(false);
|
||||
localConfirmationLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationState addListener(InvitationListener l) {
|
||||
lock.lock();
|
||||
try {
|
||||
listeners.add(l);
|
||||
return new InvitationState(localInvitationCode,
|
||||
remoteInvitationCode, localConfirmationCode,
|
||||
remoteConfirmationCode, connected.get(), connectionFailed,
|
||||
localCompared, remoteCompared, localMatched, remoteMatched,
|
||||
remoteName);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvitationListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LocalAuthor localAuthor;
|
||||
// Load the local pseudonym
|
||||
try {
|
||||
localAuthor = identityManager.getLocalAuthor();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
return;
|
||||
}
|
||||
// Start the connection threads
|
||||
Collection<Connector> connectors = new ArrayList<Connector>();
|
||||
// Alice is the party with the smaller invitation code
|
||||
if (localInvitationCode < remoteInvitationCode) {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createAliceConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
} else {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createBobConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
}
|
||||
// Wait for the connection threads to finish
|
||||
try {
|
||||
for (Connector c : connectors) c.join();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for connectors");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
// If none of the threads connected, inform the listeners
|
||||
if (!connected.get()) {
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private Connector createAliceConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
|
||||
remoteInvitationCode);
|
||||
return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
private Connector createBobConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
|
||||
localInvitationCode);
|
||||
return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
boolean getAndSetConnected() {
|
||||
boolean redundant = connected.getAndSet(true);
|
||||
if (!redundant)
|
||||
for (InvitationListener l : listeners) l.connectionSucceeded();
|
||||
return redundant;
|
||||
}
|
||||
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode) {
|
||||
lock.lock();
|
||||
try {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.keyAgreementSucceeded(localCode, remoteCode);
|
||||
}
|
||||
|
||||
void keyAgreementFailed() {
|
||||
for (InvitationListener l : listeners) l.keyAgreementFailed();
|
||||
}
|
||||
|
||||
boolean waitForLocalConfirmationResult() throws InterruptedException {
|
||||
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
|
||||
lock.lock();
|
||||
try {
|
||||
return localMatched;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void remoteConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationSucceeded();
|
||||
}
|
||||
|
||||
void remoteConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeSucceeded(Author remoteAuthor) {
|
||||
String name = remoteAuthor.getName();
|
||||
lock.lock();
|
||||
try {
|
||||
remoteName = name;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.pseudonymExchangeSucceeded(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void duplicateContact(Author remoteAuthor) {
|
||||
// TODO differentiate
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeFailed() {
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class InvitationModule {
|
||||
|
||||
@Provides
|
||||
InvitationTaskFactory provideInvitationTaskFactory(
|
||||
InvitationTaskFactoryImpl invitationTaskFactory) {
|
||||
return invitationTaskFactory;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
InvitationTaskFactoryImpl(CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager) {
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationTask createTask(int localCode, int remoteCode) {
|
||||
return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, identityManager, pluginManager,
|
||||
localCode, remoteCode);
|
||||
}
|
||||
}
|
||||
@@ -164,14 +164,6 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
return new ArrayList<DuplexPlugin>(duplexPlugins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getInvitationPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
for (DuplexPlugin d : duplexPlugins)
|
||||
if (d.supportsInvitations()) supported.add(d);
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
@@ -291,6 +283,16 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c) {
|
||||
try {
|
||||
return transportPropertyManager.getRemoteProperties(c, id);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return new TransportProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeSettings(Settings s) {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -35,6 +34,7 @@ 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.api.plugin.LanTcpConstants.PROP_IP_PORTS;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||
|
||||
@@ -45,7 +45,6 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||
|
||||
private static final int MAX_ADDRESSES = 4;
|
||||
private static final String PROP_IP_PORTS = "ipPorts";
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
LanTcpPlugin(Executor ioExecutor, Backoff backoff,
|
||||
@@ -126,9 +125,8 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) {
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return Collections.emptyList();
|
||||
protected List<InetSocketAddress> getRemoteSocketAddresses(
|
||||
TransportProperties p) {
|
||||
return parseSocketAddresses(p.get(PROP_IP_PORTS));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -10,6 +9,7 @@ import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -25,6 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
@@ -67,11 +69,11 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
protected abstract void setLocalSocketAddress(InetSocketAddress a);
|
||||
|
||||
/**
|
||||
* Returns zero or more socket addresses for connecting to the given
|
||||
* contact.
|
||||
* Returns zero or more socket addresses for connecting to a contact with
|
||||
* the given transport properties.
|
||||
*/
|
||||
protected abstract List<InetSocketAddress> getRemoteSocketAddresses(
|
||||
ContactId c);
|
||||
TransportProperties p);
|
||||
|
||||
/**
|
||||
* Returns true if connections to the given address can be attempted.
|
||||
@@ -208,16 +210,21 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
// TODO: Pass properties to connectAndCallBack()
|
||||
for (ContactId c : callback.getRemoteProperties().keySet())
|
||||
if (!connected.contains(c)) connectAndCallBack(c);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(final ContactId c) {
|
||||
private void connectAndCallBack(final ContactId c,
|
||||
final TransportProperties p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DuplexTransportConnection d = createConnection(c);
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
@@ -229,7 +236,12 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
for (InetSocketAddress remote : getRemoteSocketAddresses(c)) {
|
||||
return createConnection(callback.getRemoteProperties(c));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
|
||||
if (!isConnectable(remote)) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
SocketAddress local = socket.getLocalSocketAddress();
|
||||
@@ -281,17 +293,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tcp;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -24,12 +25,12 @@ class TcpTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
return IoUtils.getInputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
return IoUtils.getOutputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -78,9 +77,8 @@ class WanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) {
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return Collections.emptyList();
|
||||
protected List<InetSocketAddress> getRemoteSocketAddresses(
|
||||
TransportProperties p) {
|
||||
InetSocketAddress parsed = parseSocketAddress(p.get(PROP_IP_PORT));
|
||||
if (parsed == null) return Collections.emptyList();
|
||||
return Collections.singletonList(parsed);
|
||||
|
||||
@@ -160,35 +160,52 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||
TransportId t) throws DbException {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
// Don't return properties for inactive contacts
|
||||
if (!c.isActive()) continue;
|
||||
Group g = getContactGroup(c);
|
||||
// Find the latest remote update
|
||||
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
|
||||
if (latest != null) {
|
||||
// Retrieve and parse the latest remote properties
|
||||
BdfList message = clientHelper.getMessageAsList(txn,
|
||||
latest.messageId);
|
||||
if (message == null) throw new DbException();
|
||||
remote.put(c.getId(), parseProperties(message));
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return remote;
|
||||
for (Contact c : db.getContacts(txn))
|
||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return remote;
|
||||
}
|
||||
|
||||
private TransportProperties getRemoteProperties(Transaction txn, Contact c,
|
||||
TransportId t) throws DbException {
|
||||
// Don't return properties for inactive contacts
|
||||
if (!c.isActive()) return new TransportProperties();
|
||||
Group g = getContactGroup(c);
|
||||
try {
|
||||
// Find the latest remote update
|
||||
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
|
||||
if (latest == null) return new TransportProperties();
|
||||
// Retrieve and parse the latest remote properties
|
||||
BdfList message =
|
||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||
if (message == null) throw new DbException();
|
||||
return parseProperties(message);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
TransportProperties p;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeLocalProperties(TransportId t, TransportProperties p)
|
||||
throws DbException {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.reporting;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
@@ -130,7 +131,7 @@ public class DevReportServer {
|
||||
OutputStream out = null;
|
||||
try {
|
||||
socket.setSoTimeout(SOCKET_TIMEOUT_MS);
|
||||
in = socket.getInputStream();
|
||||
in = IoUtils.getInputStream(socket);
|
||||
reportDir.mkdirs();
|
||||
reportFile = File.createTempFile(FILE_PREFIX, FILE_SUFFIX,
|
||||
reportDir);
|
||||
|
||||
@@ -93,7 +93,7 @@ class DevReporterImpl implements DevReporter {
|
||||
InputStream in = null;
|
||||
try {
|
||||
Socket s = connectToDevelopers();
|
||||
out = s.getOutputStream();
|
||||
out = IoUtils.getOutputStream(s);
|
||||
in = new FileInputStream(f);
|
||||
IoUtils.copyAndClose(in, out);
|
||||
f.delete();
|
||||
|
||||
@@ -57,8 +57,8 @@ class SocksSocket extends Socket {
|
||||
|
||||
// Connect to the proxy
|
||||
super.connect(proxy, connectToProxyTimeout);
|
||||
OutputStream out = getOutputStream();
|
||||
InputStream in = getInputStream();
|
||||
OutputStream out = IoUtils.getOutputStream(this);
|
||||
InputStream in = IoUtils.getInputStream(this);
|
||||
|
||||
// Request SOCKS 5 with no authentication
|
||||
sendMethodRequest(out);
|
||||
|
||||
@@ -29,10 +29,10 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream createInvitationStreamReader(InputStream in,
|
||||
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamReaderImpl(
|
||||
streamDecrypterFactory.createInvitationStreamDecrypter(in,
|
||||
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
||||
headerKey));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream createInvitationStreamWriter(OutputStream out,
|
||||
public OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
return new StreamWriterImpl(
|
||||
streamEncrypterFactory.createInvitationStreamEncrypter(out,
|
||||
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
||||
headerKey));
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
@@ -65,7 +65,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
new Message(messageId, groupId, timestamp, rawMessage);
|
||||
private final Metadata metadata = new Metadata();
|
||||
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
||||
private final String label = TestUtils.getRandomString(5);
|
||||
private final String label = StringUtils.getRandomString(5);
|
||||
|
||||
public ClientHelperImplTest() {
|
||||
clientHelper =
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -15,7 +16,7 @@ public class HashTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
private final String label = TestUtils.getRandomString(42);
|
||||
private final String label = StringUtils.getRandomString(42);
|
||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
|
||||
private final byte[] inputBytes2 = new byte[0];
|
||||
@@ -40,7 +41,7 @@ public class HashTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDifferentLabelsProduceDifferentHashes() {
|
||||
String label2 = TestUtils.getRandomString(42);
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
byte[] hash1 = crypto.hash(label, inputBytes, inputBytes1, inputBytes2);
|
||||
byte[] hash2 =
|
||||
crypto.hash(label2, inputBytes, inputBytes1, inputBytes2);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
@@ -11,11 +10,11 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
class PseudoRandom {
|
||||
|
||||
private final Salsa20Engine cipher = new Salsa20Engine();
|
||||
|
||||
PseudoRandomImpl(byte[] seed) {
|
||||
PseudoRandom(byte[] seed) {
|
||||
// Hash the seed to produce a 32-byte key
|
||||
byte[] key = new byte[32];
|
||||
Digest digest = new Blake2sDigest();
|
||||
@@ -26,8 +25,7 @@ class PseudoRandomImpl implements PseudoRandom {
|
||||
cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextBytes(int length) {
|
||||
byte[] nextBytes(int length) {
|
||||
byte[] in = new byte[length], out = new byte[length];
|
||||
cipher.processBytes(in, 0, length, out, 0);
|
||||
return out;
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
@@ -19,7 +17,7 @@ class PseudoSecureRandom extends SecureRandom {
|
||||
private final PseudoRandom pseudoRandom;
|
||||
|
||||
private PseudoSecureRandomSpi(byte[] seed) {
|
||||
pseudoRandom = new PseudoRandomImpl(seed);
|
||||
pseudoRandom = new PseudoRandom(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -18,7 +19,7 @@ public class SignatureTest extends BrambleTestCase {
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
private final byte[] publicKey, privateKey;
|
||||
private final String label = TestUtils.getRandomString(42);
|
||||
private final String label = StringUtils.getRandomString(42);
|
||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||
|
||||
public SignatureTest() {
|
||||
@@ -64,7 +65,7 @@ public class SignatureTest extends BrambleTestCase {
|
||||
public void testDifferentLabelsProduceDifferentSignatures()
|
||||
throws Exception {
|
||||
// Generate a second label
|
||||
String label2 = TestUtils.getRandomString(42);
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
// Calculate the signature with different inputs
|
||||
// the results should be different
|
||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||
@@ -100,7 +101,7 @@ public class SignatureTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testDifferentLabelFailsVerification() throws Exception {
|
||||
// Generate a second label
|
||||
String label2 = TestUtils.getRandomString(42);
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
// calculate the signature with different label, should fail to verify
|
||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
|
||||
|
||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -159,7 +158,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testReadString8() throws Exception {
|
||||
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
// "foo", the empty string, and 127 random letters
|
||||
setContents("41" + "03" + "666F6F" + "41" + "00" +
|
||||
@@ -181,7 +180,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testSkipString8() throws Exception {
|
||||
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
// "foo", the empty string, and 127 random letters
|
||||
setContents("41" + "03" + "666F6F" + "41" + "00" +
|
||||
@@ -194,9 +193,9 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testReadString16() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
// 128 random letters and 2^15 -1 random letters
|
||||
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
|
||||
@@ -207,7 +206,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testReadString16ChecksMaxLength() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
// 128 random letters, twice
|
||||
setContents("42" + "0080" + shortHex + "42" + "0080" + shortHex);
|
||||
@@ -218,9 +217,9 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testSkipString16() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
// 128 random letters and 2^15 - 1 random letters
|
||||
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
|
||||
@@ -231,7 +230,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testReadString32() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
// 2^15 random letters
|
||||
setContents("44" + "00008000" + shortHex);
|
||||
@@ -241,7 +240,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testReadString32ChecksMaxLength() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
// 2^15 random letters, twice
|
||||
setContents("44" + "00008000" + shortHex +
|
||||
@@ -253,7 +252,7 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testSkipString32() throws Exception {
|
||||
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
// 2^15 random letters, twice
|
||||
setContents("44" + "00008000" + shortHex +
|
||||
|
||||
@@ -81,7 +81,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteString8() throws IOException {
|
||||
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Byte.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
w.writeString("foo bar baz bam ");
|
||||
w.writeString(longest);
|
||||
@@ -93,9 +93,9 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteString16() throws IOException {
|
||||
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Byte.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longest = StringUtils.getRandomString(Short.MAX_VALUE);
|
||||
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
|
||||
w.writeString(shortest);
|
||||
w.writeString(longest);
|
||||
@@ -106,7 +106,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteString32() throws IOException {
|
||||
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortest = StringUtils.getRandomString(Short.MAX_VALUE + 1);
|
||||
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
|
||||
w.writeString(shortest);
|
||||
// STRING_32 tag, length 2^15, UTF-8 bytes
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -48,8 +49,8 @@ public class BasicH2Test extends BrambleTestCase {
|
||||
createTable(connection);
|
||||
// Generate an ID and two names
|
||||
byte[] id = TestUtils.getRandomId();
|
||||
String oldName = TestUtils.getRandomString(50);
|
||||
String newName = TestUtils.getRandomString(50);
|
||||
String oldName = StringUtils.getRandomString(50);
|
||||
String newName = StringUtils.getRandomString(50);
|
||||
// Insert the ID and old name into the table
|
||||
insertRow(id, oldName);
|
||||
// Check that the old name can be retrieved using the ID
|
||||
@@ -78,8 +79,8 @@ public class BasicH2Test extends BrambleTestCase {
|
||||
String[] newNames = new String[BATCH_SIZE];
|
||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||
ids[i] = TestUtils.getRandomId();
|
||||
oldNames[i] = TestUtils.getRandomString(50);
|
||||
newNames[i] = TestUtils.getRandomString(50);
|
||||
oldNames[i] = StringUtils.getRandomString(50);
|
||||
newNames[i] = StringUtils.getRandomString(50);
|
||||
}
|
||||
// Insert the IDs and old names into the table as a batch
|
||||
insertBatch(ids, oldNames);
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
@@ -94,7 +95,7 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
||||
private final Contact contact;
|
||||
|
||||
public DatabaseComponentImplTest() {
|
||||
clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -84,7 +85,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
public H2DatabaseTest() throws Exception {
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -31,8 +32,9 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
||||
private final Transaction txn = new Transaction(null, false);
|
||||
private final LocalAuthor localAuthor =
|
||||
new LocalAuthor(new AuthorId(TestUtils.getRandomId()),
|
||||
TestUtils.getRandomString(8), TestUtils.getRandomBytes(42),
|
||||
TestUtils.getRandomBytes(42), 0);
|
||||
StringUtils.getRandomString(8),
|
||||
TestUtils.getRandomBytes(42), TestUtils.getRandomBytes(42),
|
||||
0);
|
||||
private final Collection<LocalAuthor> localAuthors =
|
||||
Collections.singletonList(localAuthor);
|
||||
|
||||
@@ -93,7 +95,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
||||
assertEquals(UNKNOWN, identityManager.getAuthorStatus(authorId));
|
||||
|
||||
// add one unverified contact
|
||||
Author author = new Author(authorId, TestUtils.getRandomString(8),
|
||||
Author author = new Author(authorId, StringUtils.getRandomString(8),
|
||||
TestUtils.getRandomBytes(42));
|
||||
Contact contact =
|
||||
new Contact(new ContactId(1), author, localAuthor.getId(),
|
||||
|
||||
@@ -311,6 +311,11 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
return remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(ContactId c) {
|
||||
return remote.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeSettings(Settings s) {
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class TransportPropertyValidatorTest extends BrambleTestCase {
|
||||
bdfDictionary = new BdfDictionary();
|
||||
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
byte[] descriptor = TestUtils.getRandomBytes(12);
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
@@ -85,7 +86,7 @@ public class TransportPropertyValidatorTest extends BrambleTestCase {
|
||||
public void testValidateLongTransportId() throws IOException {
|
||||
|
||||
String wrongTransportIdString =
|
||||
TestUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
||||
StringUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
||||
BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
|
||||
tpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -78,7 +79,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
headerKey = TestUtils.getSecretKey();
|
||||
streamNumber = 123;
|
||||
// Create a group
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
Group group = groupFactory.createGroup(clientId, descriptor);
|
||||
// Add two messages to the group
|
||||
|
||||
@@ -23,6 +23,7 @@ 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.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -51,7 +52,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
private final Executor dbExecutor = new ImmediateExecutor();
|
||||
private final Executor validationExecutor = new ImmediateExecutor();
|
||||
private final ClientId clientId =
|
||||
new ClientId(TestUtils.getRandomString(5));
|
||||
new ClientId(StringUtils.getRandomString(5));
|
||||
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
private final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
private final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||
|
||||
@@ -28,7 +29,7 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||
protected final Message message =
|
||||
new Message(messageId, groupId, timestamp, raw);
|
||||
protected final ClientId clientId =
|
||||
new ClientId(TestUtils.getRandomString(123));
|
||||
new ClientId(StringUtils.getRandomString(123));
|
||||
protected final byte[] descriptor = TestUtils.getRandomBytes(123);
|
||||
protected final Group group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ targetCompatibility = 1.7
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-core')
|
||||
compile project(path: ':bramble-core', configuration: 'default')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'net.java.dev.jna:jna:4.4.0'
|
||||
compile 'net.java.dev.jna:jna-platform:4.4.0'
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -19,33 +18,23 @@ import org.briarproject.bramble.util.OsUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
@@ -67,7 +56,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
@@ -261,8 +249,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!running) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
@@ -273,95 +260,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
if (!running) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving invitation connections
|
||||
final StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
CompletionService<StreamConnection> complete =
|
||||
new ExecutorCompletionService<>(ioExecutor);
|
||||
List<Future<StreamConnection>> futures = new ArrayList<>();
|
||||
if (alice) {
|
||||
// Return the first connected socket
|
||||
futures.add(complete.submit(new ListeningTask(ss)));
|
||||
futures.add(complete.submit(new DiscoveryTask(uuid)));
|
||||
} else {
|
||||
// Return the first socket with readable data
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new ListeningTask(ss))));
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new DiscoveryTask(uuid))));
|
||||
}
|
||||
StreamConnection chosen = null;
|
||||
try {
|
||||
Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS);
|
||||
if (f == null) return null; // No task completed within the timeout
|
||||
chosen = f.get();
|
||||
return new BluetoothTransportConnection(this, chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSockets(final List<Future<StreamConnection>> futures,
|
||||
@Nullable final StreamConnection chosen) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<StreamConnection> f : futures) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else {
|
||||
StreamConnection s = f.get();
|
||||
if (s != null && s != chosen) {
|
||||
LOG.info("Closing unwanted socket");
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
return;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -376,7 +274,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving invitation connections
|
||||
// Bind a server socket for receiving key agreementconnections
|
||||
final StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
@@ -431,77 +329,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryTask implements Callable<StreamConnection> {
|
||||
|
||||
private final String uuid;
|
||||
|
||||
private DiscoveryTask(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
// Repeat discovery until we connect or get interrupted
|
||||
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
|
||||
while (true) {
|
||||
if (!discoverySemaphore.tryAcquire())
|
||||
throw new Exception("Discovery is already in progress");
|
||||
try {
|
||||
InvitationListener listener =
|
||||
new InvitationListener(discoveryAgent, uuid);
|
||||
discoveryAgent.startInquiry(GIAC, listener);
|
||||
String url = listener.waitForUrl();
|
||||
if (url != null) {
|
||||
StreamConnection s = connect(url);
|
||||
if (s != null) {
|
||||
LOG.info("Outgoing connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
discoverySemaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListeningTask implements Callable<StreamConnection> {
|
||||
|
||||
private final StreamConnectionNotifier serverSocket;
|
||||
|
||||
private ListeningTask(StreamConnectionNotifier serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
StreamConnection s = serverSocket.acceptAndOpen();
|
||||
LOG.info("Incoming connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableTask implements Callable<StreamConnection> {
|
||||
|
||||
private final Callable<StreamConnection> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<StreamConnection> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamConnection call() throws Exception {
|
||||
StreamConnection s = connectionTask.call();
|
||||
InputStream in = s.openInputStream();
|
||||
while (in.available() == 0) {
|
||||
LOG.info("Waiting for data");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
LOG.info("Data available");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final StreamConnectionNotifier ss;
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.DataElement;
|
||||
import javax.bluetooth.DeviceClass;
|
||||
import javax.bluetooth.DiscoveryAgent;
|
||||
import javax.bluetooth.DiscoveryListener;
|
||||
import javax.bluetooth.RemoteDevice;
|
||||
import javax.bluetooth.ServiceRecord;
|
||||
import javax.bluetooth.UUID;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class InvitationListener implements DiscoveryListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(InvitationListener.class.getName());
|
||||
|
||||
private final AtomicInteger searches = new AtomicInteger(1);
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final DiscoveryAgent discoveryAgent;
|
||||
private final String uuid;
|
||||
|
||||
private volatile String url = null;
|
||||
|
||||
InvitationListener(DiscoveryAgent discoveryAgent, String uuid) {
|
||||
this.discoveryAgent = discoveryAgent;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
String waitForUrl() throws InterruptedException {
|
||||
finished.await();
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
|
||||
UUID[] uuids = new UUID[] {new UUID(uuid, false)};
|
||||
// Try to discover the services associated with the UUID
|
||||
try {
|
||||
discoveryAgent.searchServices(null, uuids, device, this);
|
||||
searches.incrementAndGet();
|
||||
} catch (BluetoothStateException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void servicesDiscovered(int transaction, ServiceRecord[] services) {
|
||||
for (ServiceRecord record : services) {
|
||||
// Does this service have a URL?
|
||||
String serviceUrl = record.getConnectionURL(
|
||||
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
|
||||
if (serviceUrl == null) continue;
|
||||
// Does this service have the UUID we're looking for?
|
||||
Collection<String> uuids = new TreeSet<>();
|
||||
findNestedClassIds(record.getAttributeValue(0x1), uuids);
|
||||
for (String u : uuids) {
|
||||
if (uuid.equalsIgnoreCase(u)) {
|
||||
// The UUID matches - store the URL
|
||||
url = serviceUrl;
|
||||
finished.countDown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inquiryCompleted(int discoveryType) {
|
||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceSearchCompleted(int transaction, int response) {
|
||||
if (searches.decrementAndGet() == 0) finished.countDown();
|
||||
}
|
||||
|
||||
// UUIDs are sometimes buried in nested data elements
|
||||
private void findNestedClassIds(Object o, Collection<String> ids) {
|
||||
o = getDataElementValue(o);
|
||||
if (o instanceof Enumeration<?>) {
|
||||
for (Object o1 : Collections.list((Enumeration<?>) o))
|
||||
findNestedClassIds(o1, ids);
|
||||
} else if (o instanceof UUID) {
|
||||
ids.add(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDataElementValue(Object o) {
|
||||
if (o instanceof DataElement) {
|
||||
// Bluecove throws an exception if the type is unknown
|
||||
try {
|
||||
return ((DataElement) o).getValue();
|
||||
} catch (ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.modem;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -146,8 +145,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
String fromIso = callback.getLocalProperties().get("iso3166");
|
||||
if (StringUtils.isNullOrEmpty(fromIso)) return null;
|
||||
// Get the ISO 3166 code for the callee's country
|
||||
TransportProperties properties = callback.getRemoteProperties().get(c);
|
||||
if (properties == null) return null;
|
||||
TransportProperties properties = callback.getRemoteProperties(c);
|
||||
String toIso = properties.get("iso3166");
|
||||
if (StringUtils.isNullOrEmpty(toIso)) return null;
|
||||
// Get the callee's phone number
|
||||
@@ -167,17 +165,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
return new ModemTransportConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
|
||||
@@ -9,8 +9,6 @@ import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -65,12 +63,10 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
final TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
final ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -82,7 +78,7 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(returnValue(true));
|
||||
@@ -106,12 +102,10 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
final TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
final ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -123,7 +117,7 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(returnValue(false));
|
||||
@@ -147,12 +141,10 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
final TransportProperties remote = new TransportProperties();
|
||||
remote.put("iso3166", ISO_1336);
|
||||
remote.put("number", NUMBER);
|
||||
final ContactId contactId = new ContactId(234);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
@@ -164,7 +156,7 @@ public class ModemPluginTest extends BrambleTestCase {
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
oneOf(callback).getRemoteProperties(contactId);
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER);
|
||||
will(throwException(new IOException()));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr, nb_NO: nb, zh-Hans: zh-rCN
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
|
||||
@@ -3,8 +3,8 @@ apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
def supportVersion = '23.2.1'
|
||||
compile project(':briar-core')
|
||||
compile project(':bramble-android')
|
||||
compile project(path: ':briar-core', configuration: 'default')
|
||||
compile project(path: ':bramble-android', configuration: 'default')
|
||||
compile "com.android.support:support-v4:$supportVersion"
|
||||
compile("com.android.support:appcompat-v7:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
@@ -73,20 +73,24 @@ def getGitHash = { ->
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
buildToolsVersion '25.0.0'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1602
|
||||
versionName "0.16.2"
|
||||
versionCode 1611
|
||||
versionName "0.16.11"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_name", "Briar Beta"
|
||||
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
|
||||
resValue "string", "app_name", "Briar Debug"
|
||||
shrinkResources false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
@@ -103,6 +107,10 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
cruncherEnabled = false
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
warning 'MissingTranslation'
|
||||
warning 'ImpliedQuantity'
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:name="org.briarproject.briar.android.BriarApplicationImpl"
|
||||
@@ -34,6 +32,11 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="org.briarproject.briar.android.NotificationCleanupService"
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
|
||||
android:excludeFromRecents="true"
|
||||
@@ -292,16 +295,6 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.invitation.AddContactActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
@@ -27,7 +26,6 @@ import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
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;
|
||||
@@ -45,6 +43,7 @@ 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.briarproject.briar.api.test.TestDataCreator;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||
|
||||
@@ -74,8 +73,6 @@ public interface AndroidComponent
|
||||
|
||||
DatabaseConfig databaseConfig();
|
||||
|
||||
ReferenceManager referenceMangager();
|
||||
|
||||
@DatabaseExecutor
|
||||
Executor databaseExecutor();
|
||||
|
||||
@@ -89,8 +86,6 @@ public interface AndroidComponent
|
||||
|
||||
EventBus eventBus();
|
||||
|
||||
InvitationTaskFactory invitationTaskFactory();
|
||||
|
||||
AndroidNotificationManager androidNotificationManager();
|
||||
|
||||
ScreenFilterMonitor screenFilterMonitor();
|
||||
@@ -143,6 +138,8 @@ public interface AndroidComponent
|
||||
|
||||
Clock clock();
|
||||
|
||||
TestDataCreator testDataCreator();
|
||||
|
||||
@IoExecutor
|
||||
Executor ioExecutor();
|
||||
|
||||
@@ -154,6 +151,8 @@ public interface AndroidComponent
|
||||
|
||||
void inject(RecentEmojiPageModel recentEmojiPageModel);
|
||||
|
||||
void inject(NotificationCleanupService notificationCleanupService);
|
||||
|
||||
// Eager singleton load
|
||||
void inject(AppModule.EagerSingletons init);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -25,12 +23,14 @@ import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.contact.ConversationActivity;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.util.BriarNotificationBuilder;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
|
||||
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
|
||||
@@ -48,6 +48,7 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -61,8 +62,6 @@ import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE;
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.contact.ConversationActivity.CONTACT_ID;
|
||||
@@ -85,15 +84,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
||||
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
||||
|
||||
// Content URIs to differentiate between pending intents
|
||||
private static final String CONTACT_URI =
|
||||
"content://org.briarproject.briar/contact";
|
||||
private static final String GROUP_URI =
|
||||
"content://org.briarproject.briar/group";
|
||||
private static final String FORUM_URI =
|
||||
"content://org.briarproject.briar/forum";
|
||||
private static final String BLOG_URI =
|
||||
"content://org.briarproject.briar/blog";
|
||||
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
|
||||
@@ -102,6 +93,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private final SettingsManager settingsManager;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final Clock clock;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// The following must only be accessed on the main UI thread
|
||||
@@ -117,16 +109,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private boolean blockContacts = false, blockGroups = false;
|
||||
private boolean blockForums = false, blockBlogs = false;
|
||||
private boolean blockIntroductions = false;
|
||||
private long lastSound = 0;
|
||||
|
||||
private volatile Settings settings = new Settings();
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManagerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
SettingsManager settingsManager, AndroidExecutor androidExecutor,
|
||||
Application app) {
|
||||
Application app, Clock clock) {
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.settingsManager = settingsManager;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.clock = clock;
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@@ -265,7 +259,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (count == null) contactCounts.put(c, 1);
|
||||
else contactCounts.put(c, count + 1);
|
||||
contactTotal++;
|
||||
updateContactNotification();
|
||||
updateContactNotification(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -278,32 +272,30 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
Integer count = contactCounts.remove(c);
|
||||
if (count == null) return; // Already cleared
|
||||
contactTotal -= count;
|
||||
updateContactNotification();
|
||||
updateContactNotification(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateContactNotification() {
|
||||
private void updateContactNotification(boolean mayAlertAgain) {
|
||||
if (contactTotal == 0) {
|
||||
clearContactNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_message);
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setColorRes(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(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);
|
||||
b.setNumber(contactTotal);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, CONTACT_URI);
|
||||
if (contactCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant conversation
|
||||
Intent i = new Intent(appContext, ConversationActivity.class);
|
||||
@@ -326,21 +318,25 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_MESSAGE);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void setAlertProperties(BriarNotificationBuilder b) {
|
||||
long currentTime = clock.currentTimeMillis();
|
||||
if (currentTime - lastSound > SOUND_DELAY) {
|
||||
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());
|
||||
lastSound = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private int getDefaults() {
|
||||
int defaults = DEFAULT_LIGHTS;
|
||||
@@ -353,6 +349,23 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private void setDeleteIntent(BriarNotificationBuilder b, String uri) {
|
||||
Intent i = new Intent(appContext, NotificationCleanupService.class);
|
||||
i.setData(Uri.parse(uri));
|
||||
b.setDeleteIntent(PendingIntent.getService(appContext, nextRequestId++,
|
||||
i, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllContactNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearContactNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showGroupMessageNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -364,7 +377,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (count == null) groupCounts.put(g, 1);
|
||||
else groupCounts.put(g, count + 1);
|
||||
groupTotal++;
|
||||
updateGroupMessageNotification();
|
||||
updateGroupMessageNotification(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -377,31 +390,30 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
Integer count = groupCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
groupTotal -= count;
|
||||
updateGroupMessageNotification();
|
||||
updateGroupMessageNotification(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateGroupMessageNotification() {
|
||||
private void updateGroupMessageNotification(boolean mayAlertAgain) {
|
||||
if (groupTotal == 0) {
|
||||
clearGroupMessageNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_group);
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setColorRes(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(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
b.setNumber(groupTotal);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, GROUP_URI);
|
||||
if (groupCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant group
|
||||
Intent i = new Intent(appContext, GroupActivity.class);
|
||||
@@ -425,21 +437,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(GROUP_MESSAGE_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllGroupMessageNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearGroupMessageNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showForumPostNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -451,7 +464,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (count == null) forumCounts.put(g, 1);
|
||||
else forumCounts.put(g, count + 1);
|
||||
forumTotal++;
|
||||
updateForumPostNotification();
|
||||
updateForumPostNotification(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -464,31 +477,30 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
Integer count = forumCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
forumTotal -= count;
|
||||
updateForumPostNotification();
|
||||
updateForumPostNotification(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateForumPostNotification() {
|
||||
private void updateForumPostNotification(boolean mayAlertAgain) {
|
||||
if (forumTotal == 0) {
|
||||
clearForumPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_forum);
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setColorRes(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(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
b.setNumber(forumTotal);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, FORUM_URI);
|
||||
if (forumCounts.size() == 1) {
|
||||
// Touching the notification shows the relevant forum
|
||||
Intent i = new Intent(appContext, ForumActivity.class);
|
||||
@@ -512,21 +524,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(FORUM_POST_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllForumPostNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearForumPostNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showBlogPostNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@@ -538,7 +551,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (count == null) blogCounts.put(g, 1);
|
||||
else blogCounts.put(g, count + 1);
|
||||
blogTotal++;
|
||||
updateBlogPostNotification();
|
||||
updateBlogPostNotification(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -551,31 +564,30 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
Integer count = blogCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
blogTotal -= count;
|
||||
updateBlogPostNotification();
|
||||
updateBlogPostNotification(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateBlogPostNotification() {
|
||||
private void updateBlogPostNotification(boolean mayAlertAgain) {
|
||||
if (blogTotal == 0) {
|
||||
clearBlogPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_blog);
|
||||
b.setColor(ContextCompat.getColor(appContext,
|
||||
R.color.briar_primary));
|
||||
b.setColorRes(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(PREF_NOTIFY_RINGTONE_URI);
|
||||
if (!StringUtils.isNullOrEmpty(ringtoneUri))
|
||||
b.setSound(Uri.parse(ringtoneUri));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setOnlyAlertOnce(true);
|
||||
b.setAutoCancel(true);
|
||||
b.setNumber(blogTotal);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, BLOG_URI);
|
||||
// Touching the notification shows the combined blog feed
|
||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||
i.putExtra(INTENT_BLOGS, true);
|
||||
@@ -585,15 +597,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addParentStack(NavDrawerActivity.class);
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SOCIAL);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(BLOG_POST_NOTIFICATION_ID, b.build());
|
||||
@@ -623,20 +627,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
@UiThread
|
||||
private void updateIntroductionNotification() {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
BriarNotificationBuilder b = new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_introduction);
|
||||
b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
b.setContentText(appContext.getResources().getQuantityString(
|
||||
R.plurals.introduction_notification_text, introductionTotal,
|
||||
introductionTotal));
|
||||
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);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen);
|
||||
setAlertProperties(b);
|
||||
setDeleteIntent(b, INTRODUCTION_URI);
|
||||
// Touching the notification shows the contact list
|
||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||
i.putExtra(INTENT_CONTACTS, true);
|
||||
@@ -646,20 +648,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addParentStack(NavDrawerActivity.class);
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_MESSAGE);
|
||||
boolean showOnLockScreen =
|
||||
settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
|
||||
if (showOnLockScreen)
|
||||
b.setVisibility(VISIBILITY_PRIVATE);
|
||||
else
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllIntroductionNotifications() {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
clearIntroductionSuccessNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockNotification(final GroupId g) {
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
|
||||
@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
|
||||
*/
|
||||
public interface BriarApplication {
|
||||
|
||||
// This build expires on 21 October 2017
|
||||
long EXPIRY_DATE = 1508544000 * 1000L;
|
||||
// This build expires on 31 December 2017
|
||||
long EXPIRY_DATE = 1514761200 * 1000L;
|
||||
|
||||
AndroidComponent getApplicationComponent();
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.StrictMode;
|
||||
import android.os.StrictMode.ThreadPolicy;
|
||||
import android.os.StrictMode.VmPolicy;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.ReportingInteractionMode;
|
||||
@@ -33,6 +36,8 @@ import static org.acra.ReportField.REPORT_ID;
|
||||
import static org.acra.ReportField.STACK_TRACE;
|
||||
import static org.acra.ReportField.USER_APP_START_DATE;
|
||||
import static org.acra.ReportField.USER_CRASH_DATE;
|
||||
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
|
||||
@ReportsCrashes(
|
||||
reportPrimerClass = BriarReportPrimer.class,
|
||||
@@ -72,6 +77,9 @@ public class BriarApplicationImpl extends Application
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
|
||||
LOG.info("Created");
|
||||
|
||||
applicationComponent = DaggerAndroidComponent.builder()
|
||||
@@ -85,6 +93,17 @@ public class BriarApplicationImpl extends Application
|
||||
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
|
||||
threadPolicy.detectAll();
|
||||
threadPolicy.penaltyLog();
|
||||
StrictMode.setThreadPolicy(threadPolicy.build());
|
||||
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
|
||||
vmPolicy.detectAll();
|
||||
vmPolicy.penaltyLog();
|
||||
StrictMode.setVmPolicy(vmPolicy.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidComponent getApplicationComponent() {
|
||||
return applicationComponent;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_URI;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_URI;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_URI;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_URI;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.INTRODUCTION_URI;
|
||||
|
||||
public class NotificationCleanupService extends IntentService {
|
||||
|
||||
private static final String TAG =
|
||||
NotificationCleanupService.class.getName();
|
||||
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
|
||||
public NotificationCleanupService() {
|
||||
super(TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
AndroidComponent applicationComponent =
|
||||
((BriarApplication) getApplication()).getApplicationComponent();
|
||||
applicationComponent.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable Intent i) {
|
||||
if (i == null || i.getData() == null) return;
|
||||
String uri = i.getData().toString();
|
||||
if (uri.equals(CONTACT_URI)) {
|
||||
notificationManager.clearAllContactNotifications();
|
||||
} else if (uri.equals(GROUP_URI)) {
|
||||
notificationManager.clearAllGroupMessageNotifications();
|
||||
} else if (uri.equals(FORUM_URI)) {
|
||||
notificationManager.clearAllForumPostNotifications();
|
||||
} else if (uri.equals(BLOG_URI)) {
|
||||
notificationManager.clearAllBlogPostNotifications();
|
||||
} else if (uri.equals(INTRODUCTION_URI)) {
|
||||
notificationManager.clearAllIntroductionNotifications();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,21 @@ import static java.util.logging.Level.OFF;
|
||||
public interface TestingConstants {
|
||||
|
||||
/**
|
||||
* Whether this is an alpha or beta build. This should be set to false for
|
||||
* Whether this is a debug build.
|
||||
*/
|
||||
boolean IS_DEBUG_BUILD = BuildConfig.DEBUG;
|
||||
|
||||
/**
|
||||
* Whether this is a beta build. This should be set to false for final
|
||||
* release builds.
|
||||
*/
|
||||
boolean TESTING = BuildConfig.DEBUG;
|
||||
boolean IS_BETA_BUILD = true;
|
||||
|
||||
/** Default log level. */
|
||||
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;
|
||||
/**
|
||||
* Default log level. Disable logging for final release builds.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
|
||||
|
||||
/**
|
||||
* Whether to prevent screenshots from being taken. Setting this to true
|
||||
@@ -24,5 +32,5 @@ public interface TestingConstants {
|
||||
* Unfortunately this also prevents the user from taking screenshots
|
||||
* intentionally.
|
||||
*/
|
||||
boolean PREVENT_SCREENSHOTS = !TESTING;
|
||||
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||
import org.briarproject.briar.android.invitation.AddContactActivity;
|
||||
import org.briarproject.briar.android.keyagreement.IntroFragment;
|
||||
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
|
||||
import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
||||
@@ -52,6 +51,7 @@ import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
|
||||
import org.briarproject.briar.android.settings.SettingsActivity;
|
||||
import org.briarproject.briar.android.settings.SettingsFragment;
|
||||
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
|
||||
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
|
||||
import org.briarproject.briar.android.sharing.ForumInvitationActivity;
|
||||
@@ -91,8 +91,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(PanicPreferencesActivity activity);
|
||||
|
||||
void inject(AddContactActivity activity);
|
||||
|
||||
void inject(KeyAgreementActivity activity);
|
||||
|
||||
void inject(ConversationActivity activity);
|
||||
@@ -182,4 +180,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(IntroductionMessageFragment fragment);
|
||||
|
||||
void inject(SettingsFragment fragment);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ public interface RequestCodes {
|
||||
int REQUEST_WRITE_BLOG_POST = 6;
|
||||
int REQUEST_SHARE_BLOG = 7;
|
||||
int REQUEST_RINGTONE = 8;
|
||||
int REQUEST_PERMISSION_CAMERA = 9;
|
||||
|
||||
}
|
||||
|
||||
@@ -60,8 +60,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
false);
|
||||
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
ui = new BlogPostViewHolder(view);
|
||||
ui.setOnBlogPostClickListener(new OnBlogPostClickListener() {
|
||||
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
|
||||
@Override
|
||||
public void onBlogPostClick(BlogPostItem post) {
|
||||
// We're already there
|
||||
|
||||
@@ -106,7 +106,7 @@ class BlogControllerImpl extends BaseControllerImpl
|
||||
BlogInvitationResponseReceivedEvent b =
|
||||
(BlogInvitationResponseReceivedEvent) e;
|
||||
InvitationResponse r = b.getResponse();
|
||||
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
|
||||
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
|
||||
LOG.info("Blog invitation accepted");
|
||||
onBlogInvitationAccepted(b.getContactId());
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ class BlogPostAdapter
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v);
|
||||
ui.setOnBlogPostClickListener(listener);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
|
||||
return ui;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,16 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.blog.BaseController.BlogListener;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BlogPostFragment extends BasePostFragment {
|
||||
public class BlogPostFragment extends BasePostFragment implements BlogListener {
|
||||
|
||||
private static final String TAG = BlogPostFragment.class.getName();
|
||||
|
||||
@@ -40,6 +42,7 @@ public class BlogPostFragment extends BasePostFragment {
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
blogController.setBlogListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,4 +62,15 @@ public class BlogPostFragment extends BasePostFragment {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
|
||||
// doesn't matter here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogRemoved() {
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
@@ -47,12 +48,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ImageView reblogButton;
|
||||
private final TextView body;
|
||||
private final ViewGroup commentContainer;
|
||||
private final boolean fullText;
|
||||
|
||||
@Nullable
|
||||
private OnBlogPostClickListener listener;
|
||||
@NonNull
|
||||
private final OnBlogPostClickListener listener;
|
||||
|
||||
BlogPostViewHolder(View v) {
|
||||
BlogPostViewHolder(View v, boolean fullText,
|
||||
@NonNull OnBlogPostClickListener listener) {
|
||||
super(v);
|
||||
this.fullText = fullText;
|
||||
this.listener = listener;
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = (ViewGroup) v.findViewById(R.id.postLayout);
|
||||
@@ -64,10 +69,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
(ViewGroup) v.findViewById(R.id.commentContainer);
|
||||
}
|
||||
|
||||
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void setVisibility(int visibility) {
|
||||
layout.setVisibility(visibility);
|
||||
}
|
||||
@@ -92,7 +93,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
if (item == null) return;
|
||||
|
||||
setTransitionName(item.getId());
|
||||
if (listener != null) {
|
||||
if (!fullText) {
|
||||
layout.setClickable(true);
|
||||
layout.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -111,7 +112,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
author.setPersona(
|
||||
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
||||
// TODO make author clickable more often #624
|
||||
if (listener != null && item.getHeader().getType() == POST) {
|
||||
if (!fullText && item.getHeader().getType() == POST) {
|
||||
author.setAuthorClickable(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -124,7 +125,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
// post body
|
||||
Spanned bodyText = getSpanned(item.getBody());
|
||||
if (listener == null) {
|
||||
if (fullText) {
|
||||
body.setText(bodyText);
|
||||
body.setTextIsSelectable(true);
|
||||
makeLinksClickable(body);
|
||||
@@ -170,7 +171,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
reblogger.setAuthor(item.getAuthor());
|
||||
reblogger.setAuthorStatus(item.getAuthorStatus());
|
||||
reblogger.setDate(item.getTimestamp());
|
||||
if (listener != null) {
|
||||
if (!fullText) {
|
||||
reblogger.setAuthorClickable(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -200,7 +201,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
// TODO make author clickable #624
|
||||
|
||||
body.setText(c.getComment());
|
||||
if (listener == null) body.setTextIsSelectable(true);
|
||||
if (fullText) body.setTextIsSelectable(true);
|
||||
|
||||
commentContainer.addView(v);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ public class ReblogActivity extends BriarActivity implements
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setSceneTransitionAnimation();
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
byte[] groupId = intent.getByteArrayExtra(GROUP_ID);
|
||||
|
||||
@@ -161,7 +161,18 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
private ViewHolder(View v) {
|
||||
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
|
||||
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
|
||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
|
||||
true, new OnBlogPostClickListener() {
|
||||
@Override
|
||||
public void onBlogPostClick(BlogPostItem post) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
// probably don't want to allow author clicks here
|
||||
}
|
||||
});
|
||||
input = (TextInputView) v.findViewById(R.id.inputText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
eventBus.addListener(this);
|
||||
notificationManager.clearAllContactNotifications();
|
||||
notificationManager.clearAllIntroductionNotifications();
|
||||
loadContacts();
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@@ -186,8 +186,8 @@ public class ConversationActivity extends BriarActivity
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
setSceneTransitionAnimation();
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
int id = i.getIntExtra(CONTACT_ID, -1);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -43,10 +44,10 @@ public class CreateForumActivity extends BriarActivity {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CreateForumActivity.class.getName());
|
||||
|
||||
private TextInputLayout nameEntryLayout;
|
||||
private EditText nameEntry;
|
||||
private Button createForumButton;
|
||||
private ProgressBar progress;
|
||||
private TextView feedback;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
@@ -58,6 +59,8 @@ public class CreateForumActivity extends BriarActivity {
|
||||
|
||||
setContentView(R.layout.activity_create_forum);
|
||||
|
||||
nameEntryLayout =
|
||||
(TextInputLayout) findViewById(R.id.createForumNameLayout);
|
||||
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
|
||||
nameEntry.addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@@ -85,8 +88,6 @@ public class CreateForumActivity extends BriarActivity {
|
||||
}
|
||||
});
|
||||
|
||||
feedback = (TextView) findViewById(R.id.createForumFeedback);
|
||||
|
||||
createForumButton = (Button) findViewById(R.id.createForumButton);
|
||||
createForumButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -118,10 +119,10 @@ public class CreateForumActivity extends BriarActivity {
|
||||
String name = nameEntry.getText().toString();
|
||||
int length = StringUtils.toUtf8(name).length;
|
||||
if (length > MAX_FORUM_NAME_LENGTH) {
|
||||
feedback.setText(R.string.name_too_long);
|
||||
nameEntryLayout.setError(getString(R.string.name_too_long));
|
||||
return false;
|
||||
}
|
||||
feedback.setText("");
|
||||
nameEntryLayout.setError(null);
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
@@ -41,7 +40,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BOD
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ForumActivity extends
|
||||
ThreadListActivity<Forum, ThreadItemAdapter<ForumItem>, ForumItem, ForumPostHeader>
|
||||
ThreadListActivity<Forum, ForumItem, ThreadItemAdapter<ForumItem>>
|
||||
implements ForumListener {
|
||||
|
||||
@Inject
|
||||
@@ -53,7 +52,7 @@ public class ForumActivity extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadListController<Forum, ForumItem, ForumPostHeader> getController() {
|
||||
protected ThreadListController<Forum, ForumItem> getController() {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
|
||||
@NotNullByDefault
|
||||
interface ForumController
|
||||
extends ThreadListController<Forum, ForumItem, ForumPostHeader> {
|
||||
interface ForumController extends ThreadListController<Forum, ForumItem> {
|
||||
|
||||
interface ForumListener extends ThreadListListener<ForumPostHeader> {
|
||||
interface ForumListener extends ThreadListListener<ForumItem> {
|
||||
@UiThread
|
||||
void onForumLeft(ContactId c);
|
||||
}
|
||||
|
||||
@@ -75,25 +75,25 @@ class ForumControllerImpl extends
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof ForumPostReceivedEvent) {
|
||||
ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e;
|
||||
if (pe.getGroupId().equals(getGroupId())) {
|
||||
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
|
||||
if (f.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum post received, adding...");
|
||||
onForumPostHeaderReceived(pe.getForumPostHeader());
|
||||
onForumPostReceived(f.getHeader(), f.getBody());
|
||||
}
|
||||
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
ForumInvitationResponseReceivedEvent f =
|
||||
(ForumInvitationResponseReceivedEvent) e;
|
||||
ForumInvitationResponse r =
|
||||
(ForumInvitationResponse) f.getResponse();
|
||||
if (r.getGroupId().equals(getGroupId()) && r.wasAccepted()) {
|
||||
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
|
||||
LOG.info("Forum invitation was accepted");
|
||||
onForumInvitationAccepted(r.getContactId());
|
||||
}
|
||||
} else if (e instanceof ContactLeftShareableEvent) {
|
||||
ContactLeftShareableEvent s = (ContactLeftShareableEvent) e;
|
||||
if (s.getGroupId().equals(getGroupId())) {
|
||||
ContactLeftShareableEvent c = (ContactLeftShareableEvent) e;
|
||||
if (c.getGroupId().equals(getGroupId())) {
|
||||
LOG.info("Forum left by contact");
|
||||
onForumLeft(s.getContactId());
|
||||
onForumLeft(c.getContactId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,11 +195,12 @@ class ForumControllerImpl extends
|
||||
return new ForumItem(header, body);
|
||||
}
|
||||
|
||||
private void onForumPostHeaderReceived(final ForumPostHeader h) {
|
||||
private void onForumPostReceived(ForumPostHeader h, String body) {
|
||||
final ForumItem item = buildItem(h, body);
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onHeaderReceived(h);
|
||||
listener.onItemReceived(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user