Compare commits

..

47 Commits

Author SHA1 Message Date
Torsten Grote
af469d7f27 [android] add real Tor icon 2020-01-29 11:27:50 -03:00
Torsten Grote
0192b64dd3 [android] Scroll down when nav drawer chevron is pressed 2020-01-29 11:26:21 -03:00
Torsten Grote
aeb148c600 [android] remove unused strings 2020-01-29 11:26:20 -03:00
Torsten Grote
ac288b01d1 [android] make transport plugin toggles functional 2020-01-24 14:19:25 -03:00
Torsten Grote
197b40647c [android] Add transport plugin toggles to NavDrawer 2020-01-24 11:12:34 -03:00
akwizgran
9fc4bc838d Merge branch 'plugin-manager-plugin-state' into 'plugin-toggles'
Add method for enabling/disabling plugins to PluginManager

See merge request briar/briar!1214
2020-01-24 12:56:28 +00:00
Torsten Grote
89c227e2da [bramble] Add method for enabling/disabling plugins to PluginManager 2020-01-24 09:18:11 -03:00
Torsten Grote
4269bd4b74 Merge branch 'plugin-toggle-settings' into 'plugin-toggles'
Add toggle settings for transport plugins

See merge request briar/briar!1211
2020-01-23 15:09:19 +00:00
akwizgran
cc7a19402e Remove another redundant call to pluginStateChanged(). 2020-01-23 13:24:37 +00:00
akwizgran
dc64c4148d Enable LAN plugin before showing QR code. 2020-01-23 13:18:38 +00:00
akwizgran
e647ae7bb4 Remove redundant call to pluginStateChanged(). 2020-01-23 12:51:41 +00:00
Torsten Grote
42776f56d0 Merge branch 'skip-fetching-feeds-if-tor-is-not-active' into 'plugin-toggles'
Skip fetching RSS feeds if Tor is not active

See merge request briar/briar!1212
2020-01-20 18:23:30 +00:00
Torsten Grote
559776b0f5 Merge branch 'amber-transport-icons' into 'plugin-toggles'
Use amber icon when enabling transports

See merge request briar/briar!1210
2020-01-20 18:22:10 +00:00
Torsten Grote
642485d7bd Merge branch '581-plugin-states' into 'plugin-toggles'
Better methods for querying plugin states

See merge request briar/briar!1209
2020-01-20 18:19:10 +00:00
akwizgran
070be8621d Use XML to specify dependencies between settings. 2020-01-20 16:41:39 +00:00
akwizgran
2e42fb3c44 Only update bridge and padding settings if network is enabled. 2020-01-20 16:20:36 +00:00
akwizgran
3f0f3746d7 Skip fetching RSS feeds if Tor is not active. 2020-01-20 15:40:24 +00:00
akwizgran
c2dd61b006 Clean up logic for enabling/disabling settings. 2020-01-20 15:12:38 +00:00
akwizgran
5e37b3da22 Don't remove old settings yet.
This avoids an unlikely race condition at startup, where the user opens
the settings screen before the Tor plugin has migrated the settings.
2020-01-20 15:12:38 +00:00
akwizgran
8622f663f6 Enable LAN plugin in unit test. 2020-01-20 15:12:38 +00:00
akwizgran
628b69d4eb Enable BT plugin before showing QR code. 2020-01-20 15:12:38 +00:00
akwizgran
b53319a7b0 Small code cleanups in key agreement UI. 2020-01-20 15:12:38 +00:00
akwizgran
98d4a48855 Make REASON_USER into a generic reason code. 2020-01-20 15:12:38 +00:00
akwizgran
9184bf6afc Add toggle setting for LAN plugin. 2020-01-20 15:12:37 +00:00
akwizgran
4f2f145ab6 Update semantics of Bluetooth setting.
The setting now enables/disables the plugin, not just contact
connections. The key agreement UI will need to be updated to change the
setting if the user agrees to use Bluetooth.
2020-01-20 15:12:37 +00:00
akwizgran
c945b3f611 Convert Bluetooth setting to a switch. 2020-01-20 15:12:37 +00:00
akwizgran
0940b8d5b9 Add toggle setting for Tor plugin. 2020-01-20 15:12:37 +00:00
akwizgran
dac21cb3a0 Remove redundant casts. 2020-01-20 15:00:44 +00:00
akwizgran
9bfbb4d02d Notify callback of state changes while holding lock. 2020-01-20 15:00:16 +00:00
akwizgran
2689e5f361 Update javadocs for lock-safe methods. 2020-01-20 14:48:33 +00:00
akwizgran
d7d8af7e32 Remove redundant logging. 2020-01-20 14:03:12 +00:00
akwizgran
57a47463d6 Use amber icon when enabling transports. 2020-01-17 12:39:52 +00:00
akwizgran
8db481a17a Remove debug logging. 2020-01-17 12:38:43 +00:00
akwizgran
2b9ffc7fbe Close server socket when BT is disabled. 2020-01-17 12:38:03 +00:00
akwizgran
0a5f93edf9 Remove unnecessary inner class, state checks. 2020-01-16 13:08:16 +00:00
akwizgran
0aada89625 Reset backoff before notifying of new state.
The new state may cause the poller to poll the
plugin. Let's avoid a race between updating and
querying the polling interval.
2020-01-16 13:01:41 +00:00
akwizgran
549cf4e2be Move to enabling state earlier in Tor startup. 2020-01-16 12:38:34 +00:00
akwizgran
c6981fb243 Add TransportStateEvent, rename existing events. 2020-01-16 11:54:28 +00:00
akwizgran
10791aea49 Ensure server socket is closed. 2020-01-16 11:35:32 +00:00
akwizgran
1c98d8f12a Add method for getting reason why plugin is disabled. 2020-01-16 11:05:36 +00:00
akwizgran
6bce4b76d2 Fix test expectations. 2020-01-16 11:05:02 +00:00
akwizgran
c7565cb93e Rename available/unavailable states. 2020-01-16 09:58:12 +00:00
akwizgran
32288c376b Update tests. 2020-01-16 09:47:49 +00:00
akwizgran
1e7a1670dd If adapter is disabled, forget that we enabled it. 2020-01-15 17:51:18 +00:00
akwizgran
850ad18a36 Check that server sockets are closed as expected. 2020-01-15 17:51:18 +00:00
akwizgran
5d6ed1a724 Provide more information about plugin states. 2020-01-15 17:51:18 +00:00
akwizgran
ded1792213 Avoid NPE if there's no TelephonyManager. 2020-01-14 09:51:03 +00:00
173 changed files with 2921 additions and 2778 deletions

View File

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 10207 versionCode 10205
versionName "1.2.7" versionName "1.2.5"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,7 +38,7 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.10@zip' tor 'org.briarproject:tor-android:0.3.5.9@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip' tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -21,7 +20,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir; import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
@@ -32,12 +30,6 @@ class AndroidAccountManager extends AccountManagerImpl
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidAccountManager.class.getName()); Logger.getLogger(AndroidAccountManager.class.getName());
/**
* Directories that shouldn't be deleted when deleting the user's account.
*/
private static final List<String> PROTECTED_DIR_NAMES =
asList("cache", "code_cache", "lib", "shared_prefs");
protected final Context appContext; protected final Context appContext;
private final SharedPreferences prefs; private final SharedPreferences prefs;
@@ -89,7 +81,7 @@ class AndroidAccountManager extends AccountManagerImpl
if (!prefs.edit().clear().commit()) if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences"); LOG.warning("Could not clear shared preferences");
} }
// Delete files, except protected directories // Delete files, except lib and shared_prefs directories
Set<File> files = new HashSet<>(); Set<File> files = new HashSet<>();
File dataDir = getDataDir(); File dataDir = getDataDir();
@Nullable @Nullable
@@ -98,12 +90,14 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not list files in app data dir"); LOG.warning("Could not list files in app data dir");
} else { } else {
for (File file : fileArray) { for (File file : fileArray) {
if (!PROTECTED_DIR_NAMES.contains(file.getName())) { String name = file.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
files.add(file); files.add(file);
} }
} }
} }
files.add(appContext.getFilesDir()); files.add(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir()); addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) { if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) { for (File file : appContext.getExternalCacheDirs()) {
@@ -115,16 +109,12 @@ class AndroidAccountManager extends AccountManagerImpl
addIfNotNull(files, file); addIfNotNull(files, file);
} }
} }
// Clear the cache directory but don't delete it
File cacheDir = appContext.getCacheDir();
File[] children = cacheDir.listFiles();
if (children != null) files.addAll(asList(children));
for (File file : files) { for (File file : files) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting " + file.getAbsolutePath());
}
deleteFileOrDir(file); deleteFileOrDir(file);
} }
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdirs())
LOG.warning("Could not recreate cache dir");
} }
private File getDataDir() { private File getDataDir() {

View File

@@ -32,7 +32,6 @@ import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
@@ -77,9 +76,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(ACTION_SCREEN_ON); filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF); filter.addAction(ACTION_SCREEN_OFF);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
} }
@Override @Override
@@ -137,8 +136,7 @@ class AndroidNetworkManager implements NetworkManager, Service {
} }
private boolean isApEvent(@Nullable String action) { private boolean isApEvent(@Nullable String action) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(action) || return WIFI_AP_STATE_CHANGED_ACTION.equals(action);
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action);
} }
} }
} }

View File

@@ -24,6 +24,7 @@ import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
@@ -46,10 +47,7 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF; import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND; import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -148,6 +146,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
wasEnabledByUs = true; wasEnabledByUs = true;
} }
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override @Override
@Nullable @Nullable
String getBluetoothAddress() { String getBluetoothAddress() {
@@ -242,15 +246,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
break; break;
} else if (ACTION_FOUND.equals(action)) { } else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE); BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
// Ignore Bluetooth LE devices String address = d.getAddress();
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) { if (LOG.isLoggable(INFO))
String address = d.getAddress(); LOG.info("Discovered " + scrubMacAddress(address));
if (LOG.isLoggable(INFO)) if (!addresses.contains(address))
LOG.info("Discovered " + addresses.add(address);
scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
} }
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
} }
@@ -266,7 +266,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
// Shuffle the addresses so we don't always try the same one first // Shuffle the addresses so we don't always try the same one first
shuffle(addresses); Collections.shuffle(addresses);
return addresses; return addresses;
} }

View File

@@ -10,9 +10,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress;
@NotNullByDefault @NotNullByDefault
class AndroidBluetoothTransportConnection class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
@@ -26,8 +23,6 @@ class AndroidBluetoothTransportConnection
super(plugin); super(plugin);
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.socket = socket; this.socket = socket;
String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
} }
@Override @Override

View File

@@ -9,17 +9,17 @@ import android.net.wifi.WifiManager;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.settings.Settings;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -32,14 +32,31 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName()); getLogger(AndroidLanTcpPlugin.class.getName());
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
private final ConnectivityManager connectivityManager; private final ConnectivityManager connectivityManager;
@Nullable @Nullable
@@ -49,9 +66,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext, AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency, Backoff backoff, PluginCallback callback, int maxLatency,
int maxIdleTime, int connectionTimeout) { int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
connectionTimeout);
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1); new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1);
@@ -67,36 +83,27 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override @Override
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty(); Settings settings = callback.getSettings();
running = true; state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
updateConnectionStatus(); updateConnectionStatus();
} }
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override @Override
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return socketFactory.createSocket(); return socketFactory.createSocket();
} }
@Override @Override
protected List<InetAddress> getUsableLocalInetAddresses() { protected Collection<InetAddress> getLocalIpAddresses() {
// If the device doesn't have wifi, don't open any sockets // If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList(); if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, return its address // If we're connected to a wifi network, use that network
WifiInfo info = wifiManager.getConnectionInfo(); WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) { if (info != null && info.getIpAddress() != 0)
return singletonList(intToInetAddress(info.getIpAddress())); return singletonList(intToInetAddress(info.getIpAddress()));
}
// If we're running an access point, return its address // If we're running an access point, return its address
for (InetAddress addr : getLocalInetAddresses()) { if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
if (addr.equals(WIFI_AP_ADDRESS)) return singletonList(addr); return singletonList(WIFI_AP_ADDRESS);
if (addr.equals(WIFI_DIRECT_AP_ADDRESS)) return singletonList(addr);
}
// No suitable addresses // No suitable addresses
return emptyList(); return emptyList();
} }
@@ -130,30 +137,37 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof NetworkStatusEvent) updateConnectionStatus(); if (e instanceof NetworkStatusEvent) updateConnectionStatus();
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; State s = getState();
List<InetAddress> addrs = getUsableLocalInetAddresses(); if (s != ACTIVE && s != INACTIVE) return;
if (addrs.contains(WIFI_AP_ADDRESS) Collection<InetAddress> addrs = getLocalIpAddresses();
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) { if (addrs.contains(WIFI_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot"); LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way // There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to // to get a suitable socket factory, so we won't be able to
// make outgoing connections on API 21+ if another network // make outgoing connections on API 21+ if another network
// has internet access // has internet access
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} else if (addrs.isEmpty()) { } else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi"); LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
tryToClose(socket); // Server socket may not have been closed automatically when
// interface was taken down. Socket will be cleared and state
// updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server socket");
tryToClose(state.getServerSocket(), LOG, WARNING);
}
} else { } else {
LOG.info("Connected to wifi"); LOG.info("Connected to wifi");
socketFactory = getSocketFactory(); socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} }
}); });
} }

View File

@@ -21,11 +21,10 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -56,8 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor, AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME, appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME);
CONNECTION_TIMEOUT);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -74,7 +74,6 @@ class AndroidTorPlugin extends TorPlugin {
@Override @Override
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!running) return;
if (enable) wakeLock.acquire(); if (enable) wakeLock.acquire();
super.enableNetwork(enable); super.enableNetwork(enable);
if (!enable) wakeLock.release(); if (!enable) wakeLock.release();

View File

@@ -61,12 +61,12 @@ class AndroidLocationUtils implements LocationUtils {
private String getCountryFromPhoneNetwork() { private String getCountryFromPhoneNetwork() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getNetworkCountryIso(); return tm == null ? "" : tm.getNetworkCountryIso();
} }
private String getCountryFromSimCard() { private String getCountryFromSimCard() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getSimCountryIso(); return tm == null ? "" : tm.getSimCountryIso();
} }
} }

View File

@@ -71,7 +71,7 @@ public class AndroidUtils {
return new Pair<>("", ""); return new Pair<>("", "");
} }
public static boolean isValidBluetoothAddress(@Nullable String address) { private static boolean isValidBluetoothAddress(@Nullable String address) {
return !StringUtils.isNullOrEmpty(address) return !StringUtils.isNullOrEmpty(address)
&& BluetoothAdapter.checkBluetoothAddress(address) && BluetoothAdapter.checkBluetoothAddress(address)
&& !address.equals(FAKE_BLUETOOTH_ADDRESS); && !address.equals(FAKE_BLUETOOTH_ADDRESS);

View File

@@ -72,9 +72,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
@Test @Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles() public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception { throws Exception {
// Directories 'code_cache', 'lib' and 'shared_prefs' should be spared // Directories 'lib' and 'shared_prefs' should be spared
File codeCacheDir = new File(testDir, "code_cache");
File codeCacheFile = new File(codeCacheDir, "file");
File libDir = new File(testDir, "lib"); File libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file"); File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs"); File sharedPrefsDir = new File(testDir, "shared_prefs");
@@ -113,8 +111,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(dbDir.mkdirs()); assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs()); assertTrue(keyDir.mkdirs());
assertTrue(codeCacheDir.mkdirs());
assertTrue(codeCacheFile.createNewFile());
assertTrue(libDir.mkdirs()); assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile()); assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs()); assertTrue(sharedPrefsDir.mkdirs());
@@ -130,8 +126,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(dbDir.exists()); assertFalse(dbDir.exists());
assertFalse(keyDir.exists()); assertFalse(keyDir.exists());
assertTrue(codeCacheDir.exists());
assertTrue(codeCacheFile.exists());
assertTrue(libDir.exists()); assertTrue(libDir.exists());
assertTrue(libFile.exists()); assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists()); assertTrue(sharedPrefsDir.exists());

View File

@@ -70,7 +70,7 @@ dependencyVerification {
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a', 'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.10:tor-android-0.3.5.10.zip:edd83bf557fcff2105eaa0bdb3f607a6852ebe7360920929ae3039dd5f4774c5', 'org.briarproject:tor-android:0.3.5.9:tor-android-0.3.5.9.zip:853b0440feccd6904bd03e6b2de53a62ebcde1d58068beeadc447a7dff950bc8',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0', 'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.api.account; package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -14,8 +13,7 @@ public interface AccountManager {
* Returns true if the manager has the database key. This will be false * Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String, String)} or {@link #signIn(String)} * before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and true after {@link #createAccount(String, String)} * has been called, and true after {@link #createAccount(String, String)}
* or {@link #signIn(String)} has returned true, until * or {@link #signIn(String)} has returned true, until the process exits.
* {@link #deleteAccount()} is called or the process exits.
*/ */
boolean hasDatabaseKey(); boolean hasDatabaseKey();
@@ -24,22 +22,25 @@ public interface AccountManager {
* before {@link #createAccount(String, String)} or {@link #signIn(String)} * before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and non-null after * has been called, and non-null after
* {@link #createAccount(String, String)} or {@link #signIn(String)} has * {@link #createAccount(String, String)} or {@link #signIn(String)} has
* returned true, until {@link #deleteAccount()} is called or the process * returned true, until the process exits.
* exits.
*/ */
@Nullable @Nullable
SecretKey getDatabaseKey(); SecretKey getDatabaseKey();
/** /**
* Returns true if the encrypted database key can be loaded from disk. * Returns true if the encrypted database key can be loaded from disk, and
* the database directory exists and is a directory.
*/ */
boolean accountExists(); boolean accountExists();
/** /**
* Creates an identity with the given name and registers it with the * Creates an identity with the given name and registers it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the * {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk. {@link #accountExists()} will * given password and stores it on disk.
* return true after this method returns true. * <p/>
* This method does not create the database directory, so
* {@link #accountExists()} will continue to return false until the
* database directory is created.
*/ */
boolean createAccount(String name, String password); boolean createAccount(String name, String password);
@@ -53,19 +54,17 @@ public interface AccountManager {
* Loads the encrypted database key from disk and decrypts it with the * Loads the encrypted database key from disk and decrypts it with the
* given password. * given password.
* *
* @throws DecryptionException If the database key could not be loaded and * @return true if the database key was successfully loaded and decrypted.
* decrypted.
*/ */
void signIn(String password) throws DecryptionException; boolean signIn(String password);
/** /**
* Loads the encrypted database key from disk, decrypts it with the old * Loads the encrypted database key from disk, decrypts it with the old
* password, encrypts it with the new password, and stores it on disk, * password, encrypts it with the new password, and stores it on disk,
* replacing the old key. * replacing the old key.
* *
* @throws DecryptionException If the database key could not be loaded and * @return true if the database key was successfully loaded, re-encrypted
* decrypted. * and stored.
*/ */
void changePassword(String oldPassword, String newPassword) boolean changePassword(String oldPassword, String newPassword);
throws DecryptionException;
} }

View File

@@ -142,17 +142,16 @@ public interface CryptoComponent {
/** /**
* Decrypts and authenticates the given ciphertext that has been read from * Decrypts and authenticates the given ciphertext that has been read from
* storage. The encryption and authentication keys are derived from the * storage. The encryption and authentication keys are derived from the
* given password. * given password. Returns null if the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
* *
* @param keyStrengthener Used to strengthen the password-based key. If * @param keyStrengthener Used to strengthen the password-based key. If
* null, or if strengthening was not used when encrypting the ciphertext, * null, or if strengthening was not used when encrypting the ciphertext,
* the password-based key will not be strengthened * the password-based key will not be strengthened
* @throws DecryptionException If the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
*/ */
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password, byte[] decryptWithPassword(byte[] ciphertext, String password,
@Nullable KeyStrengthener keyStrengthener) @Nullable KeyStrengthener keyStrengthener);
throws DecryptionException;
/** /**
* Returns true if the given ciphertext was encrypted using a strengthened * Returns true if the given ciphertext was encrypted using a strengthened

View File

@@ -1,17 +0,0 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class DecryptionException extends Exception {
private final DecryptionResult result;
public DecryptionException(DecryptionResult result) {
this.result = result;
}
public DecryptionResult getDecryptionResult() {
return result;
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.api.crypto;
/**
* The result of a password-based decryption operation.
*/
public enum DecryptionResult {
/**
* Decryption succeeded.
*/
SUCCESS,
/**
* Decryption failed because the format of the ciphertext was invalid.
*/
INVALID_CIPHERTEXT,
/**
* Decryption failed because the {@link KeyStrengthener} used for
* encryption was not available for decryption.
*/
KEY_STRENGTHENER_ERROR,
/**
* Decryption failed because the password used for decryption did not match
* the password used for encryption.
*/
INVALID_PASSWORD
}

View File

@@ -18,6 +18,8 @@ public interface EventBus {
/** /**
* Asynchronously notifies all listeners of an event. Listeners are * Asynchronously notifies all listeners of an event. Listeners are
* notified on the {@link EventExecutor}. * notified on the {@link EventExecutor}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void broadcast(Event e); void broadcast(Event e);
} }

View File

@@ -9,5 +9,6 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address"; String PROP_ADDRESS = "address";
String PROP_UUID = "uuid"; String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable"; // Reason code returned by Plugin#getReasonDisabled()
int REASON_NO_BT_ADAPTER = 2;
} }

View File

@@ -5,7 +5,8 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
@@ -20,15 +21,15 @@ public interface ConnectionRegistry {
/** /**
* Registers a connection with the given contact over the given transport. * Registers a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts * Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ConnectionStatusChangedEvent} if this is the only connection with * {@link ContactConnectedEvent} if this is the only connection with the
* the contact. * contact.
*/ */
void registerConnection(ContactId c, TransportId t, boolean incoming); void registerConnection(ContactId c, TransportId t, boolean incoming);
/** /**
* Unregisters a connection with the given contact over the given transport. * Unregisters a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts * Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ConnectionStatusChangedEvent} if this is the only connection with * {@link ContactDisconnectedEvent} if this is the only connection with
* the contact. * the contact.
*/ */
void unregisterConnection(ContactId c, TransportId t, boolean incoming); void unregisterConnection(ContactId c, TransportId t, boolean incoming);
@@ -44,9 +45,9 @@ public interface ConnectionRegistry {
boolean isConnected(ContactId c, TransportId t); boolean isConnected(ContactId c, TransportId t);
/** /**
* Returns the connection status of the given contact via all transports. * Returns true if the given contact is connected via any transport.
*/ */
ConnectionStatus getConnectionStatus(ContactId c); boolean isConnected(ContactId c);
/** /**
* Registers a connection with the given pending contact. Broadcasts * Registers a connection with the given pending contact. Broadcasts

View File

@@ -1,5 +0,0 @@
package org.briarproject.bramble.api.plugin;
public enum ConnectionStatus {
CONNECTED, RECENTLY_CONNECTED, DISCONNECTED
}

View File

@@ -4,10 +4,10 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan"); TransportId ID = new TransportId("org.briarproject.bramble.lan");
// Transport properties (shared with contacts) // a transport property (shared with contacts)
String PROP_IP_PORTS = "ipPorts"; String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
// A local setting // a local setting
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";
} }

View File

@@ -3,12 +3,57 @@ package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.Collection; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public interface Plugin { public interface Plugin {
enum State {
/**
* The plugin has not been started, has been stopped, or is disabled by
* settings.
*/
DISABLED,
/**
* The plugin is being enabled and can't yet make or receive
* connections.
*/
ENABLING,
/**
* The plugin is enabled and can make or receive connections.
*/
ACTIVE,
/**
* The plugin is enabled but can't make or receive connections
*/
INACTIVE
}
/**
* The string for the boolean preference
* to use with the {@link SettingsManager} to enable or disable the plugin.
*/
String PREF_PLUGIN_ENABLE = "enable";
/**
* Reason code returned by {@link #getReasonDisabled()} to indicate that
* the plugin is disabled because it has not been started or has been
* stopped.
*/
int REASON_STARTING_STOPPING = 0;
/**
* Reason code returned by {@link #getReasonDisabled()} to indicate that
* the plugin has been disabled by the user.
*/
int REASON_USER = 1;
/** /**
* Returns the plugin's transport identifier. * Returns the plugin's transport identifier.
*/ */
@@ -35,9 +80,19 @@ public interface Plugin {
void stop() throws PluginException; void stop() throws PluginException;
/** /**
* Returns true if the plugin is running. * Returns the current state of the plugin.
*/ */
boolean isRunning(); State getState();
/**
* Returns an integer code indicating why the plugin is
* {@link State#DISABLED disabled}, or -1 if the plugin is not disabled.
* <p>
* The codes used are plugin-specific, except the generic codes
* {@link #REASON_STARTING_STOPPING} and {@link #REASON_USER}, which may
* be used by any plugin.
*/
int getReasonDisabled();
/** /**
* Returns true if the plugin should be polled periodically to attempt to * Returns true if the plugin should be polled periodically to attempt to

View File

@@ -1,6 +1,10 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -32,12 +36,17 @@ public interface PluginCallback extends ConnectionHandler {
void mergeLocalProperties(TransportProperties p); void mergeLocalProperties(TransportProperties p);
/** /**
* Signals that the transport is enabled. * Informs the callback of the plugin's current state.
* <p>
* If the current state is different from the previous state, the callback
* will broadcast a {@link TransportStateEvent}. If the current state is
* {@link State#ACTIVE} and the previous state was not
* {@link State#ACTIVE}, the callback will broadcast a
* {@link TransportActiveEvent}. If the current state is not
* {@link State#ACTIVE} and the previous state was {@link State#ACTIVE},
* the callback will broadcast a {@link TransportInactiveEvent}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void transportEnabled(); void pluginStateChanged(State state);
/**
* Signals that the transport is disabled.
*/
void transportDisabled();
} }

View File

@@ -41,4 +41,17 @@ public interface PluginManager {
* Returns any duplex plugins that support rendezvous. * Returns any duplex plugins that support rendezvous.
*/ */
Collection<DuplexPlugin> getRendezvousPlugins(); Collection<DuplexPlugin> getRendezvousPlugins();
/**
* Enables or disables the plugin
* identified by the given {@link TransportId}.
* <p>
* Note that this applies the change asynchronously
* and there are no order guarantees.
* <p>
* If no plugin with the given {@link TransportId} is registered,
* this is a no-op.
*/
void setPluginEnabled(TransportId t, boolean enabled);
} }

View File

@@ -23,4 +23,8 @@ public interface TorConstants {
int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
int PREF_TOR_NETWORK_NEVER = 3; int PREF_TOR_NETWORK_NEVER = 3;
// Reason codes returned by Plugin#getReasonDisabled()
int REASON_BATTERY = 2;
int REASON_MOBILE_DATA = 3;
int REASON_COUNTRY_BLOCKED = 4;
} }

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -15,8 +14,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractDuplexTransportConnection public abstract class AbstractDuplexTransportConnection
implements DuplexTransportConnection { implements DuplexTransportConnection {
protected final TransportProperties remote = new TransportProperties();
private final Plugin plugin; private final Plugin plugin;
private final Reader reader; private final Reader reader;
private final Writer writer; private final Writer writer;
@@ -47,11 +44,6 @@ public abstract class AbstractDuplexTransportConnection
return writer; return writer;
} }
@Override
public TransportProperties getRemoteProperties() {
return remote;
}
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
@Override @Override

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
/** /**
* An interface for reading and writing data over a duplex transport. The * An interface for reading and writing data over a duplex transport. The
@@ -24,10 +23,4 @@ public interface DuplexTransportConnection {
* for writing to the connection. * for writing to the connection.
*/ */
TransportConnectionWriter getWriter(); TransportConnectionWriter getWriter();
/**
* Returns a possibly empty set of {@link TransportProperties} describing
* the remote peer.
*/
TransportProperties getRemoteProperties();
} }

View File

@@ -3,31 +3,24 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a contact's connection status changes. * An event that is broadcast when a contact connects that was not previously
* connected via any transport.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class ConnectionStatusChangedEvent extends Event { public class ContactConnectedEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final ConnectionStatus status;
public ConnectionStatusChangedEvent(ContactId contactId, public ContactConnectedEvent(ContactId contactId) {
ConnectionStatus status) {
this.contactId = contactId; this.contactId = contactId;
this.status = status;
} }
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }
public ConnectionStatus getConnectionStatus() {
return status;
}
} }

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a contact disconnects and is no longer
* connected via any transport.
*/
@Immutable
@NotNullByDefault
public class ContactDisconnectedEvent extends Event {
private final ContactId contactId;
public ContactDisconnectedEvent(ContactId contactId) {
this.contactId = contactId;
}
public ContactId getContactId() {
return contactId;
}
}

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is enabled. * An event that is broadcast when a plugin enters the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportEnabledEvent extends Event { public class TransportActiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportEnabledEvent(TransportId transportId) { public TransportActiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is disabled. * An event that is broadcast when a plugin leaves the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportDisabledEvent extends Event { public class TransportInactiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportDisabledEvent(TransportId transportId) { public TransportInactiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -0,0 +1,32 @@
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.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the {@link State state} of a plugin changes.
*/
@Immutable
@NotNullByDefault
public class TransportStateEvent extends Event {
private final TransportId transportId;
private final State state;
public TransportStateEvent(TransportId transportId, State state) {
this.transportId = transportId;
this.state = state;
}
public TransportId getTransportId() {
return transportId;
}
public State getState() {
return state;
}
}

View File

@@ -11,28 +11,4 @@ public interface TransportPropertyConstants {
* The maximum length of a property's key or value in UTF-8 bytes. * The maximum length of a property's key or value in UTF-8 bytes.
*/ */
int MAX_PROPERTY_LENGTH = 100; int MAX_PROPERTY_LENGTH = 100;
/**
* Message metadata key for the transport ID of a local or remote update,
* as a BDF string.
*/
String MSG_KEY_TRANSPORT_ID = "transportId";
/**
* Message metadata key for the version number of a local or remote update,
* as a BDF long.
*/
String MSG_KEY_VERSION = "version";
/**
* Message metadata key for whether an update is local or remote, as a BDF
* boolean.
*/
String MSG_KEY_LOCAL = "local";
/**
* Group metadata key for any discovered transport properties of the
* contact, as a BDF dictionary.
*/
String GROUP_KEY_DISCOVERED = "discovered";
} }

View File

@@ -34,14 +34,6 @@ public interface TransportPropertyManager {
void addRemoteProperties(Transaction txn, ContactId c, void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException; Map<TransportId, TransportProperties> props) throws DbException;
/**
* Stores the given properties discovered from an incoming transport
* connection. They will be overridden by any properties received while
* adding the contact or synced from the contact.
*/
void addRemotePropertiesFromConnection(ContactId c, TransportId t,
TransportProperties props) throws DbException;
/** /**
* Returns the local transport properties for all transports. * Returns the local transport properties for all transports.
*/ */

View File

@@ -117,10 +117,4 @@ public class IoUtils {
throw new IOException(e); throw new IOException(e);
} }
} }
public static boolean isNonEmptyDirectory(File f) {
if (!f.isDirectory()) return false;
File[] children = f.listFiles();
return children != null && children.length > 0;
}
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -18,7 +17,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -26,7 +24,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -98,7 +95,7 @@ class AccountManagerImpl implements AccountManager {
} }
try { try {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), "UTF-8"));
String key = reader.readLine(); String key = reader.readLine();
reader.close(); reader.close();
return key; return key;
@@ -150,7 +147,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException { private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(Charset.forName("UTF-8"))); out.write(key.getBytes("UTF-8"));
out.flush(); out.flush();
out.close(); out.close();
} }
@@ -158,7 +155,8 @@ class AccountManagerImpl implements AccountManager {
@Override @Override
public boolean accountExists() { public boolean accountExists() {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
return loadEncryptedDatabaseKey() != null; return loadEncryptedDatabaseKey() != null
&& databaseConfig.getDatabaseDirectory().isDirectory();
} }
} }
@@ -195,24 +193,31 @@ class AccountManagerImpl implements AccountManager {
} }
@Override @Override
public void signIn(String password) throws DecryptionException { public boolean signIn(String password) {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
databaseKey = loadAndDecryptDatabaseKey(password); SecretKey key = loadAndDecryptDatabaseKey(password);
if (key == null) return false;
databaseKey = key;
return true;
} }
} }
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private SecretKey loadAndDecryptDatabaseKey(String password) @Nullable
throws DecryptionException { private SecretKey loadAndDecryptDatabaseKey(String password) {
String hex = loadEncryptedDatabaseKey(); String hex = loadEncryptedDatabaseKey();
if (hex == null) { if (hex == null) {
LOG.warning("Failed to load encrypted database key"); LOG.warning("Failed to load encrypted database key");
throw new DecryptionException(INVALID_CIPHERTEXT); return null;
} }
byte[] ciphertext = fromHexString(hex); byte[] ciphertext = fromHexString(hex);
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener(); KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password, byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener); keyStrengthener);
if (plaintext == null) {
LOG.info("Failed to decrypt database key");
return null;
}
SecretKey key = new SecretKey(plaintext); SecretKey key = new SecretKey(plaintext);
// If the DB key was encrypted with a weak key and a key strengthener // If the DB key was encrypted with a weak key and a key strengthener
// is now available, re-encrypt the DB key with a strengthened key // is now available, re-encrypt the DB key with a strengthened key
@@ -225,11 +230,10 @@ class AccountManagerImpl implements AccountManager {
} }
@Override @Override
public void changePassword(String oldPassword, String newPassword) public boolean changePassword(String oldPassword, String newPassword) {
throws DecryptionException {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword); SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
encryptAndStoreDatabaseKey(key, newPassword); return key != null && encryptAndStoreDatabaseKey(key, newPassword);
} }
} }
} }

View File

@@ -7,7 +7,6 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey; import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.KeyStrengthener;
@@ -40,9 +39,6 @@ import static java.lang.System.arraycopy;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -363,17 +359,16 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password, public byte[] decryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener) @Nullable KeyStrengthener keyStrengthener) {
throws DecryptionException {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes(); int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV, // The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC // ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes) { + STORAGE_IV_BYTES + macBytes)
throw new DecryptionException(INVALID_CIPHERTEXT); return null; // Invalid input
}
int inputOff = 0; int inputOff = 0;
// Format version // Format version
byte formatVersion = input[inputOff]; byte formatVersion = input[inputOff];
@@ -381,7 +376,7 @@ class CryptoComponentImpl implements CryptoComponent {
// Check whether we support this format version // Check whether we support this format version
if (formatVersion != PBKDF_FORMAT_SCRYPT && if (formatVersion != PBKDF_FORMAT_SCRYPT &&
formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) { formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
throw new DecryptionException(INVALID_CIPHERTEXT); return null;
} }
// Salt // Salt
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
@@ -390,9 +385,8 @@ class CryptoComponentImpl implements CryptoComponent {
// Cost parameter // Cost parameter
long cost = ByteUtils.readUint32(input, inputOff); long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES; inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE) { if (cost < 2 || cost > Integer.MAX_VALUE)
throw new DecryptionException(INVALID_CIPHERTEXT); return null; // Invalid cost parameter
}
// IV // IV
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
arraycopy(input, inputOff, iv, 0, iv.length); arraycopy(input, inputOff, iv, 0, iv.length);
@@ -400,10 +394,8 @@ class CryptoComponentImpl implements CryptoComponent {
// Derive the decryption key from the password // Derive the decryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost); SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) { if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
if (keyStrengthener == null || !keyStrengthener.isInitialised()) { if (keyStrengthener == null || !keyStrengthener.isInitialised())
// Can't derive the same strengthened key return null; // Can't derive the same strengthened key
throw new DecryptionException(KEY_STRENGTHENER_ERROR);
}
key = keyStrengthener.strengthenKey(key); key = keyStrengthener.strengthenKey(key);
} }
// Initialise the cipher // Initialise the cipher
@@ -419,7 +411,7 @@ class CryptoComponentImpl implements CryptoComponent {
cipher.process(input, inputOff, inputLen, output, 0); cipher.process(input, inputOff, inputLen, output, 0);
return output; return output;
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new DecryptionException(INVALID_PASSWORD); return null; // Invalid ciphertext
} }
} }

View File

@@ -25,7 +25,6 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
/** /**
@@ -70,9 +69,8 @@ class H2Database extends JdbcDatabase {
LOG.info("Contents of account directory before opening DB:"); LOG.info("Contents of account directory before opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile()); logFileOrDir(LOG, INFO, dir.getParentFile());
} }
boolean reopen = isNonEmptyDirectory(dir); boolean reopen = !dir.mkdirs();
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.h2.Driver", reopen, key, listener); super.open("org.h2.Driver", reopen, key, listener);
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:"); LOG.info("Contents of account directory after opening DB:");

View File

@@ -20,11 +20,9 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
/** /**
* Contains all the HSQLDB-specific code for the database. * Contains all the HSQLDB-specific code for the database.
@@ -66,10 +64,7 @@ class HyperSqlDatabase extends JdbcDatabase {
public boolean open(SecretKey key, @Nullable MigrationListener listener) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
this.key = key; this.key = key;
File dir = config.getDatabaseDirectory(); boolean reopen = !config.getDatabaseDirectory().mkdirs();
boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener); super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
return reopen; return reopen;
} }

View File

@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
@@ -54,7 +52,6 @@ class ConnectionManagerImpl implements ConnectionManager {
private final HandshakeManager handshakeManager; private final HandshakeManager handshakeManager;
private final ContactExchangeManager contactExchangeManager; private final ContactExchangeManager contactExchangeManager;
private final ConnectionRegistry connectionRegistry; private final ConnectionRegistry connectionRegistry;
private final TransportPropertyManager transportPropertyManager;
@Inject @Inject
ConnectionManagerImpl(@IoExecutor Executor ioExecutor, ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
@@ -63,8 +60,7 @@ class ConnectionManagerImpl implements ConnectionManager {
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
HandshakeManager handshakeManager, HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager, ContactExchangeManager contactExchangeManager,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry) {
TransportPropertyManager transportPropertyManager) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.keyManager = keyManager; this.keyManager = keyManager;
this.streamReaderFactory = streamReaderFactory; this.streamReaderFactory = streamReaderFactory;
@@ -73,7 +69,6 @@ class ConnectionManagerImpl implements ConnectionManager {
this.handshakeManager = handshakeManager; this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager; this.contactExchangeManager = contactExchangeManager;
this.connectionRegistry = connectionRegistry; this.connectionRegistry = connectionRegistry;
this.transportPropertyManager = transportPropertyManager;
} }
@Override @Override
@@ -274,7 +269,6 @@ class ConnectionManagerImpl implements ConnectionManager {
private final TransportId transportId; private final TransportId transportId;
private final TransportConnectionReader reader; private final TransportConnectionReader reader;
private final TransportConnectionWriter writer; private final TransportConnectionWriter writer;
private final TransportProperties remote;
@Nullable @Nullable
private volatile SyncSession outgoingSession = null; private volatile SyncSession outgoingSession = null;
@@ -284,7 +278,6 @@ class ConnectionManagerImpl implements ConnectionManager {
this.transportId = transportId; this.transportId = transportId;
reader = connection.getReader(); reader = connection.getReader();
writer = connection.getWriter(); writer = connection.getWriter();
remote = connection.getRemoteProperties();
} }
@Override @Override
@@ -320,16 +313,13 @@ class ConnectionManagerImpl implements ConnectionManager {
// Start the outgoing session on another thread // Start the outgoing session on another thread
ioExecutor.execute(() -> runOutgoingSession(contactId)); ioExecutor.execute(() -> runOutgoingSession(contactId));
try { try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Create and run the incoming session // Create and run the incoming session
createIncomingSession(ctx, reader).run(); createIncomingSession(ctx, reader).run();
reader.dispose(false, true); reader.dispose(false, true);
// Interrupt the outgoing session so it finishes cleanly // Interrupt the outgoing session so it finishes cleanly
SyncSession out = outgoingSession; SyncSession out = outgoingSession;
if (out != null) out.interrupt(); if (out != null) out.interrupt();
} catch (DbException | IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
onReadError(true); onReadError(true);
} finally { } finally {
@@ -385,7 +375,6 @@ class ConnectionManagerImpl implements ConnectionManager {
private final TransportId transportId; private final TransportId transportId;
private final TransportConnectionReader reader; private final TransportConnectionReader reader;
private final TransportConnectionWriter writer; private final TransportConnectionWriter writer;
private final TransportProperties remote;
@Nullable @Nullable
private volatile SyncSession outgoingSession = null; private volatile SyncSession outgoingSession = null;
@@ -396,7 +385,6 @@ class ConnectionManagerImpl implements ConnectionManager {
this.transportId = transportId; this.transportId = transportId;
reader = connection.getReader(); reader = connection.getReader();
writer = connection.getWriter(); writer = connection.getWriter();
remote = connection.getRemoteProperties();
} }
@Override @Override
@@ -473,16 +461,13 @@ class ConnectionManagerImpl implements ConnectionManager {
connectionRegistry.registerConnection(contactId, transportId, connectionRegistry.registerConnection(contactId, transportId,
false); false);
try { try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Create and run the incoming session // Create and run the incoming session
createIncomingSession(ctx, reader).run(); createIncomingSession(ctx, reader).run();
reader.dispose(false, true); reader.dispose(false, true);
// Interrupt the outgoing session so it finishes cleanly // Interrupt the outgoing session so it finishes cleanly
SyncSession out = outgoingSession; SyncSession out = outgoingSession;
if (out != null) out.interrupt(); if (out != null) out.interrupt();
} catch (DbException | IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
onReadError(); onReadError();
} finally { } finally {

View File

@@ -6,41 +6,30 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.DISCONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -49,30 +38,22 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
private static final Logger LOG = private static final Logger LOG =
getLogger(ConnectionRegistryImpl.class.getName()); getLogger(ConnectionRegistryImpl.class.getName());
private static final long RECENTLY_CONNECTED_MS = MINUTES.toMillis(1);
private static final long EXPIRY_INTERVAL_MS = SECONDS.toMillis(10);
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock;
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
private final Map<TransportId, Multiset<ContactId>> contactConnections; private final Map<TransportId, Multiset<ContactId>> contactConnections;
@GuardedBy("lock") @GuardedBy("lock")
private final Map<ContactId, Counter> contactCounts; private final Multiset<ContactId> contactCounts;
@GuardedBy("lock") @GuardedBy("lock")
private final Set<PendingContactId> connectedPendingContacts; private final Set<PendingContactId> connectedPendingContacts;
@Inject @Inject
ConnectionRegistryImpl(EventBus eventBus, Clock clock, ConnectionRegistryImpl(EventBus eventBus) {
@Scheduler ScheduledExecutorService scheduler) {
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock;
contactConnections = new HashMap<>(); contactConnections = new HashMap<>();
contactCounts = new HashMap<>(); contactCounts = new Multiset<>();
connectedPendingContacts = new HashSet<>(); connectedPendingContacts = new HashSet<>();
scheduler.scheduleWithFixedDelay(this::expireRecentConnections,
EXPIRY_INTERVAL_MS, EXPIRY_INTERVAL_MS, MILLISECONDS);
} }
@Override @Override
@@ -90,22 +71,12 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
contactConnections.put(t, m); contactConnections.put(t, m);
} }
m.add(c); m.add(c);
if (contactCounts.add(c) == 1) firstConnection = true;
Counter counter = contactCounts.get(c);
if (counter == null) {
counter = new Counter();
contactCounts.put(c, counter);
}
if (counter.connections == 0) {
counter.disconnectedTime = 0;
firstConnection = true;
}
counter.connections++;
} }
eventBus.broadcast(new ConnectionOpenedEvent(c, t, incoming)); eventBus.broadcast(new ConnectionOpenedEvent(c, t, incoming));
if (firstConnection) { if (firstConnection) {
LOG.info("Contact connected"); LOG.info("Contact connected");
eventBus.broadcast(new ConnectionStatusChangedEvent(c, CONNECTED)); eventBus.broadcast(new ContactConnectedEvent(c));
} }
} }
@@ -122,22 +93,12 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
if (m == null || !m.contains(c)) if (m == null || !m.contains(c))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
m.remove(c); m.remove(c);
if (contactCounts.remove(c) == 0) lastConnection = true;
Counter counter = contactCounts.get(c);
if (counter == null || counter.connections == 0) {
throw new IllegalArgumentException();
}
counter.connections--;
if (counter.connections == 0) {
counter.disconnectedTime = clock.currentTimeMillis();
lastConnection = true;
}
} }
eventBus.broadcast(new ConnectionClosedEvent(c, t, incoming)); eventBus.broadcast(new ConnectionClosedEvent(c, t, incoming));
if (lastConnection) { if (lastConnection) {
LOG.info("Contact disconnected"); LOG.info("Contact disconnected");
eventBus.broadcast( eventBus.broadcast(new ContactDisconnectedEvent(c));
new ConnectionStatusChangedEvent(c, RECENTLY_CONNECTED));
} }
} }
@@ -145,7 +106,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public Collection<ContactId> getConnectedContacts(TransportId t) { public Collection<ContactId> getConnectedContacts(TransportId t) {
synchronized (lock) { synchronized (lock) {
Multiset<ContactId> m = contactConnections.get(t); Multiset<ContactId> m = contactConnections.get(t);
if (m == null) return emptyList(); if (m == null) return Collections.emptyList();
List<ContactId> ids = new ArrayList<>(m.keySet()); List<ContactId> ids = new ArrayList<>(m.keySet());
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info(ids.size() + " contacts connected: " + t); LOG.info(ids.size() + " contacts connected: " + t);
@@ -162,11 +123,9 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
} }
@Override @Override
public ConnectionStatus getConnectionStatus(ContactId c) { public boolean isConnected(ContactId c) {
synchronized (lock) { synchronized (lock) {
Counter counter = contactCounts.get(c); return contactCounts.contains(c);
if (counter == null) return DISCONNECTED;
return counter.connections > 0 ? CONNECTED : RECENTLY_CONNECTED;
} }
} }
@@ -188,36 +147,4 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
} }
eventBus.broadcast(new RendezvousConnectionClosedEvent(p, success)); eventBus.broadcast(new RendezvousConnectionClosedEvent(p, success));
} }
@Scheduler
private void expireRecentConnections() {
long now = clock.currentTimeMillis();
List<ContactId> disconnected = new ArrayList<>();
synchronized (lock) {
Iterator<Entry<ContactId, Counter>> it =
contactCounts.entrySet().iterator();
while (it.hasNext()) {
Entry<ContactId, Counter> e = it.next();
if (e.getValue().isExpired(now)) {
disconnected.add(e.getKey());
it.remove();
}
}
}
for (ContactId c : disconnected) {
eventBus.broadcast(
new ConnectionStatusChangedEvent(c, DISCONNECTED));
}
}
private static class Counter {
private int connections = 0;
private long disconnectedTime = 0;
private boolean isExpired(long now) {
return connections == 0 &&
now - disconnectedTime > RECENTLY_CONNECTED_MS;
}
}
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
@@ -18,8 +19,9 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -36,6 +38,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -45,6 +48,9 @@ import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -177,6 +183,26 @@ class PluginManagerImpl implements PluginManager, Service {
return supported; return supported;
} }
@Override
public void setPluginEnabled(TransportId t, boolean enabled) {
Plugin plugin = plugins.get(t);
if (plugin == null) return;
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, enabled);
ioExecutor.execute(() -> mergeSettings(s, t.getString()));
}
private void mergeSettings(Settings s, String namespace) {
try {
long start = now();
settingsManager.mergeSettings(s, namespace);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class PluginStarter implements Runnable { private class PluginStarter implements Runnable {
private final Plugin plugin; private final Plugin plugin;
@@ -250,7 +276,8 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback { private class Callback implements PluginCallback {
private final TransportId id; private final TransportId id;
private final AtomicBoolean enabled = new AtomicBoolean(false); private final AtomicReference<State> state =
new AtomicReference<>(DISABLED);
private Callback(TransportId id) { private Callback(TransportId id) {
this.id = id; this.id = id;
@@ -278,11 +305,7 @@ class PluginManagerImpl implements PluginManager, Service {
@Override @Override
public void mergeSettings(Settings s) { public void mergeSettings(Settings s) {
try { PluginManagerImpl.this.mergeSettings(s, id.getString());
settingsManager.mergeSettings(s, id.getString());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
} }
@Override @Override
@@ -295,15 +318,20 @@ class PluginManagerImpl implements PluginManager, Service {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State newState) {
if (!enabled.getAndSet(true)) State oldState = state.getAndSet(newState);
eventBus.broadcast(new TransportEnabledEvent(id)); if (newState != oldState) {
} if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState
@Override + " to " + newState);
public void transportDisabled() { }
if (enabled.getAndSet(false)) eventBus.broadcast(new TransportStateEvent(id, newState));
eventBus.broadcast(new TransportDisabledEvent(id)); if (newState == ACTIVE) {
eventBus.broadcast(new TransportActiveEvent(id));
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
}
} }
@Override @Override

View File

@@ -20,8 +20,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
@@ -106,13 +106,13 @@ class PollerImpl implements Poller, EventListener {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e; ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased // Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId()); reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) { } else if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
// Poll the newly enabled transport // Poll the newly activated transport
pollNow(t.getTransportId()); pollNow(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) { } else if (e instanceof TransportInactiveEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e; TransportInactiveEvent t = (TransportInactiveEvent) e;
// Cancel polling for the disabled transport // Cancel polling for the deactivated transport
cancel(t.getTransportId()); cancel(t.getTransportId());
} }
} }

View File

@@ -9,7 +9,9 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -36,16 +38,21 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.REASON_NO_BT_ADAPTER;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -68,9 +75,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private final int maxLatency; private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false; protected final PluginState state = new PluginState();
private volatile String contactConnectionsUuid = null; private volatile String contactConnectionsUuid = null;
private volatile SS socket = null;
abstract void initialiseAdapter() throws IOException; abstract void initialiseAdapter() throws IOException;
@@ -119,14 +126,18 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
LOG.info("Bluetooth enabled"); LOG.info("Bluetooth enabled");
// We may not have been able to get the local address before // We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties); ioExecutor.execute(this::updateProperties);
if (shouldAllowContactConnections()) bind(); if (getState() == INACTIVE) bind();
} }
void onAdapterDisabled() { void onAdapterDisabled() {
LOG.info("Bluetooth disabled"); LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionLimiter.allConnectionsClosed(); connectionLimiter.allConnectionsClosed();
callback.transportDisabled(); // The server socket may not have been closed automatically
SS ss = state.clearServerSocket();
if (ss != null) {
LOG.info("Closing server socket");
tryToClose(ss);
}
} }
@Override @Override
@@ -151,28 +162,22 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
try { try {
initialiseAdapter(); initialiseAdapter();
} catch (IOException e) { } catch (IOException e) {
state.setNoAdapter();
throw new PluginException(e); throw new PluginException(e);
} }
updateProperties(); updateProperties();
running = true; Settings settings = callback.getSettings();
loadSettings(callback.getSettings()); boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
if (shouldAllowContactConnections()) { state.setStarted(enabledByUser);
if (enabledByUser) {
if (isAdapterEnabled()) bind(); if (isAdapterEnabled()) bind();
else enableAdapter(); else enableAdapter();
} }
} }
private void loadSettings(Settings settings) {
contactConnections = settings.getBoolean(PREF_BT_ENABLE, false);
}
private boolean shouldAllowContactConnections() {
return contactConnections;
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != INACTIVE) return;
// Bind a server socket to accept connections from contacts // Bind a server socket to accept connections from contacts
SS ss; SS ss;
try { try {
@@ -181,14 +186,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return; return;
} }
if (!isRunning() || !shouldAllowContactConnections()) { if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss); tryToClose(ss);
return; return;
} }
socket = ss;
backoff.reset(); backoff.reset();
callback.transportEnabled(); acceptContactConnections(ss);
acceptContactConnections();
}); });
} }
@@ -217,34 +221,39 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (changed) callback.mergeLocalProperties(p); if (changed) callback.mergeLocalProperties(p);
} }
private void acceptContactConnections() { private void acceptContactConnections(SS ss) {
while (true) { while (true) {
DuplexTransportConnection conn; DuplexTransportConnection conn;
try { try {
conn = acceptConnection(socket); conn = acceptConnection(ss);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket();
return; return;
} }
LOG.info("Connection received");
backoff.reset(); backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn)) if (connectionLimiter.contactConnectionOpened(conn))
callback.handleConnection(conn); callback.handleConnection(conn);
if (!running) return;
} }
} }
@Override @Override
public void stop() { public void stop() {
running = false; SS ss = state.setStopped();
tryToClose(socket); tryToClose(ss);
callback.transportDisabled();
disableAdapterIfEnabledByUs(); disableAdapterIfEnabledByUs();
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && isAdapterEnabled(); return state.getState();
}
@Override
public int getReasonDisabled() {
return state.getReasonDisabled();
} }
@Override @Override
@@ -260,7 +269,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -273,7 +282,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = p.get(PROP_UUID); String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return; if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (getState() != ACTIVE) return;
if (!connectionLimiter.canOpenContactConnection()) return; if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) {
@@ -317,7 +326,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning() || !shouldAllowContactConnections()) return null; if (getState() != ACTIVE) return null;
if (!connectionLimiter.canOpenContactConnection()) return null; if (!connectionLimiter.canOpenContactConnection()) return null;
String address = p.get(PROP_ADDRESS); String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return null; if (isNullOrEmpty(address)) return null;
@@ -336,7 +345,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16 // No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString(); String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
@@ -348,7 +357,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return null; return null;
} }
if (!isRunning()) { if (getState() != ACTIVE) {
tryToClose(ss); tryToClose(ss);
return null; return null;
} }
@@ -362,7 +371,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16 // No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString(); String uuid = UUID.nameUUIDFromBytes(commitment).toString();
DuplexTransportConnection conn; DuplexTransportConnection conn;
@@ -422,17 +431,17 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
} }
@IoExecutor
private void onSettingsUpdated(Settings settings) { private void onSettingsUpdated(Settings settings) {
boolean wasAllowed = shouldAllowContactConnections(); boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
loadSettings(settings); SS ss = state.setEnabledByUser(enabledByUser);
boolean isAllowed = shouldAllowContactConnections(); State s = getState();
if (wasAllowed && !isAllowed) { if (ss != null) {
LOG.info("Contact connections disabled"); LOG.info("Disabled by user, closing server socket");
tryToClose(socket); tryToClose(ss);
callback.transportDisabled();
disableAdapterIfEnabledByUs(); disableAdapterIfEnabledByUs();
} else if (!wasAllowed && isAllowed) { } else if (s == INACTIVE) {
LOG.info("Contact connections enabled"); LOG.info("Enabled by user, opening server socket");
if (isAdapterEnabled()) bind(); if (isAdapterEnabled()) bind();
else enableAdapter(); else enableAdapter();
} }
@@ -460,4 +469,77 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
tryToClose(ss); tryToClose(ss);
} }
} }
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
noAdapter = false,
enabledByUser = false;
@GuardedBy("this")
@Nullable
private SS serverSocket = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized SS setStopped() {
stopped = true;
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized void setNoAdapter() {
noAdapter = true;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
SS ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
synchronized boolean setServerSocket(SS ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
@Nullable
synchronized SS clearServerSocket() {
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized State getState() {
if (!started || stopped || !enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonDisabled() {
if (noAdapter && !stopped) return REASON_NO_BT_ADAPTER;
if (!started || stopped) return REASON_STARTING_STOPPING;
return enabledByUser ? -1 : REASON_USER;
}
}
} }

View File

@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -45,7 +46,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override @Override
public TransportConnectionReader createReader(TransportProperties p) { public TransportConnectionReader createReader(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String path = p.get(PROP_PATH); String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null; if (isNullOrEmpty(path)) return null;
try { try {
@@ -60,7 +61,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override @Override
public TransportConnectionWriter createWriter(TransportProperties p) { public TransportConnectionWriter createWriter(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String path = p.get(PROP_PATH); String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null; if (isNullOrEmpty(path)) return null;
try { try {

View File

@@ -11,26 +11,22 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.lang.Integer.parseInt;
import static java.util.Collections.addAll; import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort; import static java.util.Collections.sort;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -39,8 +35,8 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TR
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; 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.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.join; import static org.briarproject.bramble.util.StringUtils.join;
@@ -50,36 +46,15 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG = getLogger(LanTcpPlugin.class.getName()); private static final Logger LOG = getLogger(LanTcpPlugin.class.getName());
private static final LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4; private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ","; private static final String SEPARATOR = ",";
/**
* The IP address of an Android device providing a wifi access point.
*/
protected static final InetAddress WIFI_AP_ADDRESS;
/**
* The IP address of an Android device providing a wifi direct
* legacy mode access point.
*/
protected static final InetAddress WIFI_DIRECT_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 43, 1});
WIFI_DIRECT_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 49, 1});
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime, int connectionTimeout) { int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
connectionTimeout);
} }
@Override @Override
@@ -87,82 +62,38 @@ class LanTcpPlugin extends TcpPlugin {
return ID; return ID;
} }
@Override
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
running = true;
bind();
}
protected void initialisePortProperty() {
TransportProperties p = callback.getLocalProperties();
if (isNullOrEmpty(p.get(PROP_PORT))) {
int port = new Random().nextInt(32768) + 32768;
p.put(PROP_PORT, String.valueOf(port));
callback.mergeLocalProperties(p);
}
}
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS); String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>(); List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> fallbacks = new ArrayList<>(); for (InetAddress local : getLocalIpAddresses()) {
for (InetAddress local : getUsableLocalInetAddresses()) { if (isAcceptableAddress(local)) {
// If we've used this address before, try to use the same port // If this is the old address, try to use the same port
int port = preferredPort; for (InetSocketAddress old : olds) {
for (InetSocketAddress old : olds) { if (old.getAddress().equals(local))
if (old.getAddress().equals(local)) { locals.add(new InetSocketAddress(local, old.getPort()));
port = old.getPort();
break;
} }
locals.add(new InetSocketAddress(local, 0));
} }
locals.add(new InetSocketAddress(local, port));
// Fall back to any available port
fallbacks.add(new InetSocketAddress(local, 0));
} }
locals.addAll(fallbacks); sort(locals, ADDRESS_COMPARATOR);
return locals; return locals;
} }
private int parsePortProperty(@Nullable String portProperty) {
if (isNullOrEmpty(portProperty)) return 0;
try {
return parseInt(portProperty);
} catch (NumberFormatException e) {
return 0;
}
}
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (isNullOrEmpty(ipPorts)) return emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<>(); List<InetSocketAddress> addresses = new ArrayList<>();
if (isNullOrEmpty(ipPorts)) return addresses; for (String ipPort : split) {
for (String ipPort : ipPorts.split(SEPARATOR)) {
InetSocketAddress a = parseSocketAddress(ipPort); InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a); if (a != null) addresses.add(a);
} }
return addresses; return addresses;
} }
protected List<InetAddress> getUsableLocalInetAddresses() {
List<InterfaceAddress> ifAddrs =
new ArrayList<>(getLocalInterfaceAddresses());
// Prefer longer network prefixes
sort(ifAddrs, (a, b) ->
b.getNetworkPrefixLength() - a.getNetworkPrefixLength());
List<InetAddress> addrs = new ArrayList<>();
for (InterfaceAddress ifAddr : ifAddrs) {
InetAddress addr = ifAddr.getAddress();
if (isAcceptableAddress(addr)) addrs.add(addr);
}
return addrs;
}
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a); String ipPort = getIpPortString(a);
@@ -200,20 +131,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) { TransportProperties p) {
String ipPorts = p.get(PROP_IP_PORTS); return parseSocketAddresses(p.get(PROP_IP_PORTS));
List<InetSocketAddress> remotes = parseSocketAddresses(ipPorts);
int port = parsePortProperty(p.get(PROP_PORT));
// If the contact has a preferred port, we can guess their IP:port when
// they're providing a wifi access point
if (port != 0) {
InetSocketAddress wifiAp =
new InetSocketAddress(WIFI_AP_ADDRESS, port);
if (!remotes.contains(wifiAp)) remotes.add(wifiAp);
InetSocketAddress wifiDirectAp =
new InetSocketAddress(WIFI_DIRECT_AP_ADDRESS, port);
if (!remotes.contains(wifiDirectAp)) remotes.add(wifiDirectAp);
}
return remotes;
} }
private boolean isAcceptableAddress(InetAddress a) { private boolean isAcceptableAddress(InetAddress a) {
@@ -226,33 +144,53 @@ class LanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InetSocketAddress remote) {
InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false; if (!isAcceptableAddress(remote.getAddress())) return false;
// Try to determine whether the address is on the same LAN as us // Try to determine whether the address is on the same LAN as us
byte[] localIp = local.getAddress().getAddress(); ServerSocket ss = state.getServerSocket();
if (ss == null) return false;
byte[] localIp = ss.getInetAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress(); byte[] remoteIp = remote.getAddress().getAddress();
int prefixLength = local.getNetworkPrefixLength(); return addressesAreOnSameLan(localIp, remoteIp);
return areAddressesInSameNetwork(localIp, remoteIp, prefixLength);
} }
// Package access for testing // Package access for testing
static boolean areAddressesInSameNetwork(byte[] localIp, byte[] remoteIp, boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
int prefixLength) { // 10.0.0.0/8
if (localIp.length != remoteIp.length) return false; if (isPrefix10(localIp)) return isPrefix10(remoteIp);
// Compare the first prefixLength bits of the addresses // 172.16.0.0/12
for (int i = 0; i < prefixLength; i++) { if (isPrefix172(localIp)) return isPrefix172(remoteIp);
int byteIndex = i >> 3; // 192.168.0.0/16
int bitIndex = i & 7; // 0 to 7 if (isPrefix192(localIp)) return isPrefix192(remoteIp);
int mask = 128 >> bitIndex; // Select the bit at bitIndex // Unrecognised prefix - may be compatible
if ((localIp[byteIndex] & mask) != (remoteIp[byteIndex] & mask)) {
return false; // Addresses differ at bit i
}
}
return true; return true;
} }
private static boolean isPrefix10(byte[] ipv4) {
return ipv4[0] == 10;
}
private static boolean isPrefix172(byte[] ipv4) {
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
}
private static boolean isPrefix192(byte[] ipv4) {
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
}
// Returns the prefix length for an RFC 1918 address, or 0 for any other
// address
private static int getRfc1918PrefixLength(InetAddress addr) {
if (!(addr instanceof Inet4Address)) return 0;
if (!addr.isSiteLocalAddress()) return 0;
byte[] ipv4 = addr.getAddress();
if (isPrefix10(ipv4)) return 8;
if (isPrefix172(ipv4)) return 12;
if (isPrefix192(ipv4)) return 16;
return 0;
}
@Override @Override
public boolean supportsKeyAgreement() { public boolean supportsKeyAgreement() {
return true; return true;
@@ -271,10 +209,10 @@ class LanTcpPlugin extends TcpPlugin {
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr)); LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss); tryToClose(ss, LOG, WARNING);
} }
} }
if (ss == null || !ss.isBound()) { if (ss == null) {
LOG.info("Could not bind server socket for key agreement"); LOG.info("Could not bind server socket for key agreement");
return null; return null;
} }
@@ -290,13 +228,8 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; ServerSocket ss = state.getServerSocket();
ServerSocket ss = socket; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for key agreement server socket");
return null;
}
InetSocketAddress remote; InetSocketAddress remote;
try { try {
remote = parseSocketAddress(descriptor); remote = parseSocketAddress(descriptor);
@@ -304,7 +237,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Invalid IP/port in key agreement descriptor"); LOG.info("Invalid IP/port in key agreement descriptor");
return null; return null;
} }
if (!isConnectable(local, remote)) { if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) + LOG.info(scrubSocketAddress(remote) +
" is not connectable from " + " is not connectable from " +
@@ -317,7 +250,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket(); Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -363,7 +296,22 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public void close() { public void close() {
IoUtils.tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
}
}
static class LanAddressComparator implements Comparator<InetSocketAddress> {
@Override
public int compare(InetSocketAddress a, InetSocketAddress b) {
// Prefer addresses with non-zero ports
int aPort = a.getPort(), bPort = b.getPort();
if (aPort > 0 && bPort == 0) return -1;
if (aPort == 0 && bPort > 0) return 1;
// Prefer addresses with longer RFC 1918 prefixes
int aPrefix = getRfc1918PrefixLength(a.getAddress());
int bPrefix = getRfc1918PrefixLength(b.getAddress());
return bPrefix - aPrefix;
} }
} }
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -18,19 +19,20 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class LanTcpPluginFactory implements DuplexPluginFactory { public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public LanTcpPluginFactory(Executor ioExecutor, public LanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -48,7 +50,9 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY, LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -3,8 +3,12 @@ package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; 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.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -14,12 +18,12 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
@@ -35,19 +39,26 @@ import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class TcpPlugin implements DuplexPlugin { abstract class TcpPlugin implements DuplexPlugin, EventListener {
private static final Logger LOG = getLogger(TcpPlugin.class.getName()); private static final Logger LOG = getLogger(TcpPlugin.class.getName());
@@ -57,12 +68,9 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final Executor ioExecutor, bindExecutor; protected final Executor ioExecutor, bindExecutor;
protected final Backoff backoff; protected final Backoff backoff;
protected final PluginCallback callback; protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime; protected final int maxLatency, maxIdleTime, socketTimeout;
protected final int connectionTimeout, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false); protected final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
protected volatile boolean running = false;
protected volatile ServerSocket socket = null;
/** /**
* Returns zero or more socket addresses on which the plugin should listen, * Returns zero or more socket addresses on which the plugin should listen,
@@ -87,17 +95,15 @@ abstract class TcpPlugin implements DuplexPlugin {
* Returns true if connections to the given address can be attempted. * Returns true if connections to the given address can be attempted.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InterfaceAddress local, protected abstract boolean isConnectable(InetSocketAddress remote);
InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime, int connectionTimeout) { int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backoff = backoff; this.backoff = backoff;
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.connectionTimeout = connectionTimeout;
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; else socketTimeout = maxIdleTime * 2;
@@ -118,14 +124,14 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
running = true; Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
bind(); bind();
} }
protected void bind() { protected void bind() {
bindExecutor.execute(() -> { bindExecutor.execute(() -> {
if (!running) return; if (getState() != INACTIVE) return;
if (socket != null && !socket.isClosed()) return;
ServerSocket ss = null; ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses()) { for (InetSocketAddress addr : getLocalSocketAddresses()) {
try { try {
@@ -135,34 +141,28 @@ abstract class TcpPlugin implements DuplexPlugin {
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr)); LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss); tryToClose(ss, LOG, WARNING);
} }
} }
if (ss == null || !ss.isBound()) { if (ss == null) {
LOG.info("Could not bind server socket"); LOG.info("Could not bind server socket");
return; return;
} }
if (!running) { if (!state.setServerSocket(ss)) {
tryToClose(ss); LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return; return;
} }
socket = ss;
backoff.reset(); backoff.reset();
InetSocketAddress local = InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress(); (InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local); setLocalSocketAddress(local);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local)); LOG.info("Listening on " + scrubSocketAddress(local));
callback.transportEnabled(); acceptContactConnections(ss);
acceptContactConnections();
}); });
} }
protected void tryToClose(@Nullable ServerSocket ss) {
IoUtils.tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
}
String getIpPortString(InetSocketAddress a) { String getIpPortString(InetSocketAddress a) {
String addr = a.getAddress().getHostAddress(); String addr = a.getAddress().getHostAddress();
int percent = addr.indexOf('%'); int percent = addr.indexOf('%');
@@ -170,15 +170,16 @@ abstract class TcpPlugin implements DuplexPlugin {
return addr + ":" + a.getPort(); return addr + ":" + a.getPort();
} }
private void acceptContactConnections() { private void acceptContactConnections(ServerSocket ss) {
while (isRunning()) { while (true) {
Socket s; Socket s;
try { try {
s = socket.accept(); s = ss.accept();
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket(ss);
return; return;
} }
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -191,13 +192,18 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public void stop() { public void stop() {
running = false; ServerSocket ss = state.setStopped();
tryToClose(socket); tryToClose(ss, LOG, WARNING);
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && socket != null && !socket.isClosed(); return state.getState();
}
@Override
public int getReasonDisabled() {
return state.getReasonDisabled();
} }
@Override @Override
@@ -213,7 +219,7 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -232,20 +238,10 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; ServerSocket ss = state.getServerSocket();
ServerSocket ss = socket; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for server socket");
return null;
}
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
// Don't try to connect to our own address if (!isConnectable(remote)) {
if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) {
continue;
}
if (!isConnectable(local, remote)) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) + LOG.info(scrubSocketAddress(remote) +
" is not connectable from " + " is not connectable from " +
@@ -258,7 +254,7 @@ abstract class TcpPlugin implements DuplexPlugin {
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket(); Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -272,19 +268,6 @@ abstract class TcpPlugin implements DuplexPlugin {
return null; return null;
} }
@Nullable
InterfaceAddress getLocalInterfaceAddress(InetAddress a) {
for (InterfaceAddress ifAddr : getLocalInterfaceAddresses()) {
if (ifAddr.getAddress().equals(a)) return ifAddr;
}
return null;
}
// Override for testing
protected boolean canConnectToOwnAddress() {
return false;
}
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return new Socket(); return new Socket();
} }
@@ -340,30 +323,106 @@ abstract class TcpPlugin implements DuplexPlugin {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
List<InterfaceAddress> getLocalInterfaceAddresses() { Collection<InetAddress> getLocalIpAddresses() {
List<InterfaceAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(iface.getInterfaceAddresses());
}
return addrs;
}
List<InetAddress> getLocalInetAddresses() {
List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(list(iface.getInetAddresses()));
}
return addrs;
}
private List<NetworkInterface> getNetworkInterfaces() {
try { try {
Enumeration<NetworkInterface> ifaces = Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
NetworkInterface.getNetworkInterfaces(); if (ifaces == null) return emptyList();
return ifaces == null ? emptyList() : list(ifaces); List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : list(ifaces))
addrs.addAll(list(iface.getInetAddresses()));
return addrs;
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return emptyList(); return emptyList();
} }
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(getId().getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
}
}
@IoExecutor
private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
ServerSocket ss = state.setEnabledByUser(enabledByUser);
State s = getState();
if (ss != null) {
LOG.info("Disabled by user, closing server socket");
tryToClose(ss, LOG, WARNING);
} else if (s == INACTIVE) {
LOG.info("Enabled by user, opening server socket");
bind();
}
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false, stopped = false, enabledByUser = false;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized ServerSocket setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
ServerSocket ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized ServerSocket getServerSocket() {
return serverSocket;
}
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
callback.pluginStateChanged(getState());
}
synchronized State getState() {
if (!started || stopped || !enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonDisabled() {
if (!started || stopped) return REASON_STARTING_STOPPING;
return enabledByUser ? -1 : REASON_USER;
}
}
} }

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -30,10 +29,8 @@ class WanTcpPlugin extends TcpPlugin {
private volatile MappingResult mappingResult; private volatile MappingResult mappingResult;
WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper, WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper,
PluginCallback callback, int maxLatency, int maxIdleTime, PluginCallback callback, int maxLatency, int maxIdleTime) {
int connectionTimeout) { super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
this.portMapper = portMapper; this.portMapper = portMapper;
} }
@@ -48,7 +45,7 @@ class WanTcpPlugin extends TcpPlugin {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>(); List<InetSocketAddress> addrs = new LinkedList<>();
for (InetAddress a : getLocalInetAddresses()) { for (InetAddress a : getLocalIpAddresses()) {
if (isAcceptableAddress(a)) { if (isAcceptableAddress(a)) {
// If this is the old address, try to use the same port // If this is the old address, try to use the same port
if (old != null && old.getAddress().equals(a)) if (old != null && old.getAddress().equals(a))
@@ -89,8 +86,7 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InetSocketAddress remote) {
InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
return isAcceptableAddress(remote.getAddress()); return isAcceptableAddress(remote.getAddress());
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -19,20 +20,21 @@ import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class WanTcpPluginFactory implements DuplexPluginFactory { public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 30_000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
private final ShutdownManager shutdownManager; private final ShutdownManager shutdownManager;
public WanTcpPluginFactory(Executor ioExecutor, public WanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory, ShutdownManager shutdownManager) { BackoffFactory backoffFactory, ShutdownManager shutdownManager) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.shutdownManager = shutdownManager; this.shutdownManager = shutdownManager;
} }
@@ -51,8 +53,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new WanTcpPlugin(ioExecutor, backoff, WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff,
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY, new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
} }
} }

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -54,6 +55,9 @@ import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@@ -65,6 +69,10 @@ import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY; import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID; import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
@@ -76,6 +84,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -113,16 +124,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final int maxLatency, maxIdleTime, socketTimeout; private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile; private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final ConnectionStatus connectionStatus;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile ServerSocket socket = null; protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null; private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null; private volatile Settings settings = null;
protected volatile boolean running = false;
protected abstract int getProcessId(); protected abstract int getProcessId();
protected abstract long getLastUpdateTime(); protected abstract long getLastUpdateTime();
@@ -159,7 +168,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
configFile = new File(torDirectory, "torrc"); configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done"); doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
connectionStatus = new ConnectionStatus();
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1); new PoliteExecutor("TorPlugin", ioExecutor, 1);
@@ -183,6 +191,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
if (!torDirectory.exists()) { if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) { if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory."); LOG.warning("Could not create Tor directory.");
@@ -190,7 +199,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
// Load the settings // Load the settings
settings = callback.getSettings(); settings = migrateSettings(callback.getSettings());
// Install or update the assets if necessary // Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets(); if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete())
@@ -258,7 +267,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Tell Tor to exit when the control connection is closed // Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership(); controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER)); controlConnection.resetConf(singletonList(OWNER));
running = true;
// Register to receive events from the Tor process // Register to receive events from the Tor process
controlConnection.setEventHandler(this); controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS)); controlConnection.setEvents(asList(EVENTS));
@@ -266,11 +274,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String phase = controlConnection.getInfo("status/bootstrap-phase"); String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) { if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped"); LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped(); state.setBootstrapped();
} }
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
state.setTorStarted();
// Check whether we're online // Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
@@ -278,6 +287,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind(); bind();
} }
// TODO: Remove after a reasonable migration period (added 2020-01-16)
private Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC);
if (network == PREF_TOR_NETWORK_NEVER) {
settings.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
callback.mergeSettings(settings);
}
return settings;
}
private boolean assetsAreUpToDate() { private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }
@@ -393,11 +414,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
return; return;
} }
if (!running) { if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
return; return;
} }
socket = ss;
// Store the port number // Store the port number
String localPort = String.valueOf(ss.getLocalPort()); String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings(); Settings s = new Settings();
@@ -412,7 +433,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void publishHiddenService(String port) { private void publishHiddenService(String port) {
if (!running) return; if (!state.isTorRunning()) return;
LOG.info("Creating hidden service"); LOG.info("Creating hidden service");
String privKey = settings.get(HS_PRIVKEY); String privKey = settings.get(HS_PRIVKEY);
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port); Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
@@ -450,14 +471,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void acceptContactConnections(ServerSocket ss) { private void acceptContactConnections(ServerSocket ss) {
while (running) { while (true) {
Socket s; Socket s;
try { try {
s = ss.accept(); s = ss.accept();
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Server socket closed");
state.clearServerSocket(ss);
return; return;
} }
LOG.info("Connection received"); LOG.info("Connection received");
@@ -467,10 +489,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!running) return; state.enableNetwork(enable);
connectionStatus.enableNetwork(enable);
controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
if (!enable) callback.transportDisabled();
} }
private void enableBridges(boolean enable, boolean needsMeek) private void enableBridges(boolean enable, boolean needsMeek)
@@ -494,9 +514,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void stop() { public void stop() {
running = false; ServerSocket ss = state.setStopped();
tryToClose(socket, LOG, WARNING); tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
if (controlSocket != null && controlConnection != null) { if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); LOG.info("Stopping Tor");
@@ -510,8 +529,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running && connectionStatus.isConnected(); return state.getState();
}
@Override
public int getReasonDisabled() {
return state.getReasonDisabled();
} }
@Override @Override
@@ -527,7 +551,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (!isRunning()) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -546,7 +570,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null; if (getState() != ACTIVE) return null;
String bestOnion = null; String bestOnion = null;
String onion2 = p.get(PROP_ONION_V2); String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3); String onion3 = p.get(PROP_ONION_V3);
@@ -634,8 +658,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
new TorTransportConnection(this, s)); new TorTransportConnection(this, s));
} }
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the server socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); LOG.info("Rendezvous server socket closed");
} }
}); });
Map<Integer, String> portLines = Map<Integer, String> portLines =
@@ -663,10 +687,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") && if (status.equals("BUILT") &&
connectionStatus.getAndSetCircuitBuilt()) { state.getAndSetCircuitBuilt()) {
LOG.info("First circuit built"); LOG.info("First circuit built");
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled();
} }
} }
@@ -697,9 +720,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void message(String severity, String msg) { public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
connectionStatus.setBootstrapped(); state.setBootstrapped();
backoff.reset(); backoff.reset();
if (isRunning()) callback.transportEnabled();
} }
} }
@@ -736,7 +758,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void disableNetwork() { private void disableNetwork() {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
try { try {
enableNetwork(false); if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) { } catch (IOException ex) {
logException(LOG, WARNING, ex); logException(LOG, WARNING, ex);
} }
@@ -746,12 +768,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus(NetworkStatus status, private void updateConnectionStatus(NetworkStatus status,
boolean charging) { boolean charging) {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; if (!state.isTorRunning()) return;
boolean online = status.isConnected(); boolean online = status.isConnected();
boolean wifi = status.isWifi(); boolean wifi = status.isWifi();
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, true);
int network = settings.getInt(PREF_TOR_NETWORK, int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
@@ -762,47 +785,67 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi); LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown"); if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country); else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging); LOG.info("Charging: " + charging);
} }
try { boolean enableNetwork = false, enableBridges = false;
if (!online) { boolean useMeek = false, enableConnectionPadding = false;
LOG.info("Disabling network, device is offline"); boolean disabledBySettings = false;
enableNetwork(false); int reasonDisabled = REASON_STARTING_STOPPING;
} else if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, device is on battery"); if (!online) {
enableNetwork(false); LOG.info("Disabling network, device is offline");
} else if (network == PREF_TOR_NETWORK_NEVER || } else if (!enabledByUser) {
(!useMobile && !wifi)) { LOG.info("Disabling network, user has disabled Tor");
LOG.info("Disabling network, device is using mobile data"); disabledBySettings = true;
enableNetwork(false); reasonDisabled = REASON_USER;
} else if (automatic && blocked && !bridgesWork) { } else if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, country is blocked"); LOG.info("Disabling network, device is on battery");
enableNetwork(false); disabledBySettings = true;
} else if (network == PREF_TOR_NETWORK_WITH_BRIDGES || reasonDisabled = REASON_BATTERY;
} else if (!useMobile && !wifi) {
LOG.info("Disabling network, device is using mobile data");
disabledBySettings = true;
reasonDisabled = REASON_MOBILE_DATA;
} else if (automatic && blocked && !bridgesWork) {
LOG.info("Disabling network, country is blocked");
disabledBySettings = true;
reasonDisabled = REASON_COUNTRY_BLOCKED;
} else {
LOG.info("Enabling network");
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) { if (circumventionProvider.needsMeek(country)) {
LOG.info("Enabling network, using meek bridges"); LOG.info("Using meek bridges");
enableBridges(true, true); enableBridges = true;
useMeek = true;
} else { } else {
LOG.info("Enabling network, using obfs4 bridges"); LOG.info("Using obfs4 bridges");
enableBridges(true, false); enableBridges = true;
} }
enableNetwork(true);
} else { } else {
LOG.info("Enabling network, not using bridges"); LOG.info("Not using bridges");
enableBridges(false, false);
enableNetwork(true);
} }
if (online && wifi && charging) { if (wifi && charging) {
LOG.info("Enabling connection padding"); LOG.info("Enabling connection padding");
enableConnectionPadding(true); enableConnectionPadding = true;
} else { } else {
LOG.info("Disabling connection padding"); LOG.info("Disabling connection padding");
enableConnectionPadding(false);
} }
}
state.setDisabledBySettings(disabledBySettings, reasonDisabled);
try {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding);
}
enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -810,33 +853,100 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
if (!running) return;
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} }
private static class ConnectionStatus { @ThreadSafe
@NotNullByDefault
protected class PluginState {
// All of the following are locking: this @GuardedBy("this")
private boolean networkEnabled = false; private boolean started = false,
private boolean bootstrapped = false, circuitBuilt = false; stopped = false,
torStarted = false,
networkInitialised = false,
networkEnabled = false,
bootstrapped = false,
circuitBuilt = false,
disabledBySettings = false;
private synchronized void setBootstrapped() { @GuardedBy("this")
bootstrapped = true; private int reasonDisabled = REASON_STARTING_STOPPING;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
} }
private synchronized boolean getAndSetCircuitBuilt() { // Doesn't affect getState()
synchronized void setTorStarted() {
torStarted = true;
}
synchronized boolean isTorRunning() {
return torStarted && !stopped;
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized void setBootstrapped() {
bootstrapped = true;
callback.pluginStateChanged(getState());
}
synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt; boolean firstCircuit = !circuitBuilt;
circuitBuilt = true; circuitBuilt = true;
callback.pluginStateChanged(getState());
return firstCircuit; return firstCircuit;
} }
private synchronized void enableNetwork(boolean enable) { synchronized void enableNetwork(boolean enable) {
networkInitialised = true;
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
callback.pluginStateChanged(getState());
} }
private synchronized boolean isConnected() { synchronized void setDisabledBySettings(boolean disabledBySettings,
return networkEnabled && bootstrapped && circuitBuilt; int reasonDisabled) {
this.disabledBySettings = disabledBySettings;
this.reasonDisabled = reasonDisabled;
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
// Doesn't affect getState()
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
if (!started || stopped || disabledBySettings) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
synchronized int getReasonDisabled() {
return getState() == DISABLED ? reasonDisabled : -1;
} }
} }
} }

View File

@@ -37,11 +37,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.GROUP_KEY_DISCOVERED;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
@@ -116,10 +111,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
try { try {
// Find the latest update for this transport, if any // Find the latest update for this transport, if any
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
TransportId t = new TransportId(d.getString(MSG_KEY_TRANSPORT_ID)); TransportId t = new TransportId(d.getString("transportId"));
LatestUpdate latest = findLatest(txn, m.getGroupId(), t, false); LatestUpdate latest = findLatest(txn, m.getGroupId(), t, false);
if (latest != null) { if (latest != null) {
if (d.getLong(MSG_KEY_VERSION) > latest.version) { if (d.getLong("version") > latest.version) {
// This update is newer - delete the previous update // This update is newer - delete the previous update
db.deleteMessage(txn, latest.messageId); db.deleteMessage(txn, latest.messageId);
db.deleteMessageMetadata(txn, latest.messageId); db.deleteMessageMetadata(txn, latest.messageId);
@@ -145,27 +140,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
} }
@Override
public void addRemotePropertiesFromConnection(ContactId c, TransportId t,
TransportProperties props) throws DbException {
if (props.isEmpty()) return;
try {
db.transaction(false, txn -> {
Group g = getContactGroup(db.getContact(txn, c));
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
txn, g.getId());
BdfDictionary discovered =
meta.getOptionalDictionary(GROUP_KEY_DISCOVERED);
if (discovered == null) discovered = new BdfDictionary();
discovered.putAll(props);
meta.put(GROUP_KEY_DISCOVERED, discovered);
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
});
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override @Override
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
@@ -229,26 +203,12 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
Group g = getContactGroup(c); Group g = getContactGroup(c);
try { try {
// Find the latest remote update // Find the latest remote update
TransportProperties remote;
LatestUpdate latest = findLatest(txn, g.getId(), t, false); LatestUpdate latest = findLatest(txn, g.getId(), t, false);
if (latest == null) { if (latest == null) return new TransportProperties();
remote = new TransportProperties(); // Retrieve and parse the latest remote properties
} else { BdfList message =
// Retrieve and parse the latest remote properties clientHelper.getMessageAsList(txn, latest.messageId);
BdfList message = return parseProperties(message);
clientHelper.getMessageAsList(txn, latest.messageId);
remote = parseProperties(message);
}
// Merge in any discovered properties
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g.getId());
BdfDictionary d = meta.getOptionalDictionary(GROUP_KEY_DISCOVERED);
if (d == null) return remote;
TransportProperties merged =
clientHelper.parseAndValidateTransportProperties(d);
// Received properties override discovered properties
merged.putAll(remote);
return merged;
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
@@ -321,9 +281,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
Message m = clientHelper.createMessage(g, now, body); Message m = clientHelper.createMessage(g, now, body);
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_TRANSPORT_ID, t.getString()); meta.put("transportId", t.getString());
meta.put(MSG_KEY_VERSION, version); meta.put("version", version);
meta.put(MSG_KEY_LOCAL, local); meta.put("local", local);
clientHelper.addLocalMessage(txn, m, meta, shared, false); clientHelper.addLocalMessage(txn, m, meta, shared, false);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -342,9 +302,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
.getMessageMetadataAsDictionary(txn, localGroup.getId()); .getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue(); BdfDictionary meta = e.getValue();
TransportId t = TransportId t = new TransportId(meta.getString("transportId"));
new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID)); long version = meta.getLong("version");
long version = meta.getLong(MSG_KEY_VERSION);
latestUpdates.put(t, new LatestUpdate(e.getKey(), version)); latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
} }
return latestUpdates; return latestUpdates;
@@ -357,10 +316,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
clientHelper.getMessageMetadataAsDictionary(txn, g); clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue(); BdfDictionary meta = e.getValue();
if (meta.getString(MSG_KEY_TRANSPORT_ID).equals(t.getString()) if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean(MSG_KEY_LOCAL) == local) { && meta.getBoolean("local") == local) {
return new LatestUpdate(e.getKey(), return new LatestUpdate(e.getKey(), meta.getLong("version"));
meta.getLong(MSG_KEY_VERSION));
} }
} }
return null; return null;

View File

@@ -31,8 +31,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -269,11 +269,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
} else if (e instanceof PendingContactRemovedEvent) { } else if (e instanceof PendingContactRemovedEvent) {
PendingContactRemovedEvent p = (PendingContactRemovedEvent) e; PendingContactRemovedEvent p = (PendingContactRemovedEvent) e;
removePendingContactAsync(p.getId()); removePendingContactAsync(p.getId());
} else if (e instanceof TransportEnabledEvent) { } else if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
addTransportAsync(t.getTransportId()); addTransportAsync(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) { } else if (e instanceof TransportInactiveEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e; TransportInactiveEvent t = (TransportInactiveEvent) e;
removeTransportAsync(t.getTransportId()); removeTransportAsync(t.getTransportId());
} else if (e instanceof RendezvousConnectionOpenedEvent) { } else if (e instanceof RendezvousConnectionOpenedEvent) {
RendezvousConnectionOpenedEvent r = RendezvousConnectionOpenedEvent r =

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.reporting.DevConfig; import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
@@ -92,8 +92,8 @@ class DevReporterImpl implements DevReporter, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof TransportEnabledEvent) { if (e instanceof TransportActiveEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(TorConstants.ID)) if (t.getTransportId().equals(TorConstants.ID))
ioExecutor.execute(this::sendReports); ioExecutor.execute(this::sendReports);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.account; package org.briarproject.bramble.account;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -20,15 +19,12 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getIdentity; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -39,7 +35,6 @@ import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class AccountManagerImplTest extends BrambleMockTestCase { public class AccountManagerImplTest extends BrambleMockTestCase {
@@ -88,13 +83,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testSignInThrowsExceptionIfDbKeyCannotBeLoaded() { public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() {
try { assertFalse(accountManager.signIn(password));
accountManager.signIn(password);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
assertFalse(accountManager.hasDatabaseKey()); assertFalse(accountManager.hasDatabaseKey());
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
@@ -102,11 +92,11 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testSignInThrowsExceptionIfPasswordIsWrong() throws Exception { public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password, oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener); keyStrengthener);
will(throwException(new DecryptionException(INVALID_PASSWORD))); will(returnValue(null));
}}); }});
storeDatabaseKey(keyFile, encryptedKeyHex); storeDatabaseKey(keyFile, encryptedKeyHex);
@@ -115,12 +105,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
try { assertFalse(accountManager.signIn(password));
accountManager.signIn(password);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
assertFalse(accountManager.hasDatabaseKey()); assertFalse(accountManager.hasDatabaseKey());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
@@ -143,7 +128,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
accountManager.signIn(password); assertTrue(accountManager.signIn(password));
assertTrue(accountManager.hasDatabaseKey()); assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey(); SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted); assertNotNull(decrypted);
@@ -172,7 +157,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
accountManager.signIn(password); assertTrue(accountManager.signIn(password));
assertTrue(accountManager.hasDatabaseKey()); assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey(); SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted); assertNotNull(decrypted);
@@ -254,6 +239,55 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
} }
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(dbDir.exists());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(dbDir.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.createNewFile());
assertFalse(dbDir.isDirectory());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertFalse(dbDir.isDirectory());
}
@Test
public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.mkdirs());
assertTrue(dbDir.isDirectory());
assertTrue(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertTrue(dbDir.isDirectory());
}
@Test @Test
public void testCreateAccountStoresDbKey() throws Exception { public void testCreateAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -281,36 +315,26 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testChangePasswordThrowsExceptionIfDbKeyCannotBeLoaded() { public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() {
try { assertFalse(accountManager.changePassword(password, newPassword));
accountManager.changePassword(password, newPassword);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
} }
@Test @Test
public void testChangePasswordThrowsExceptionIfPasswordIsWrong() public void testChangePasswordReturnsFalseIfPasswordIsWrong()
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password, oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener); keyStrengthener);
will(throwException(new DecryptionException(INVALID_PASSWORD))); will(returnValue(null));
}}); }});
storeDatabaseKey(keyFile, encryptedKeyHex); storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex); storeDatabaseKey(keyBackupFile, encryptedKeyHex);
try { assertFalse(accountManager.changePassword(password, newPassword));
accountManager.changePassword(password, newPassword);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
@@ -333,7 +357,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
storeDatabaseKey(keyFile, encryptedKeyHex); storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex); storeDatabaseKey(keyBackupFile, encryptedKeyHex);
accountManager.changePassword(password, newPassword); assertTrue(accountManager.changePassword(password, newPassword));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
@@ -342,7 +366,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException { private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs(); f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes(Charset.forName("UTF-8"))); out.write(hex.getBytes("UTF-8"));
out.flush(); out.flush();
out.close(); out.close();
} }
@@ -350,7 +374,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable @Nullable
private String loadDatabaseKey(File f) throws IOException { private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), "UTF-8"));
String hex = reader.readLine(); String hex = reader.readLine();
reader.close(); reader.close();
return hex; return hex;

View File

@@ -1,35 +1,25 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.system.SystemClock; import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.jmock.Expectations; import org.briarproject.bramble.test.TestUtils;
import org.junit.Test; import org.junit.Test;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import java.util.Random;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
public class PasswordBasedEncryptionTest extends BrambleMockTestCase { public class PasswordBasedEncryptionTest extends BrambleTestCase {
private final KeyStrengthener keyStrengthener =
context.mock(KeyStrengthener.class);
private final CryptoComponentImpl crypto = private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), new CryptoComponentImpl(new TestSecureRandomProvider(),
new ScryptKdf(new SystemClock())); new ScryptKdf(new SystemClock()));
@Test @Test
public void testEncryptionAndDecryption() throws Exception { public void testEncryptionAndDecryption() {
byte[] input = getRandomBytes(1234); byte[] input = TestUtils.getRandomBytes(1234);
String password = "password"; String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password, null); byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
byte[] output = crypto.decryptWithPassword(ciphertext, password, null); byte[] output = crypto.decryptWithPassword(ciphertext, password, null);
@@ -37,80 +27,14 @@ public class PasswordBasedEncryptionTest extends BrambleMockTestCase {
} }
@Test @Test
public void testInvalidFormatVersionThrowsException() { public void testInvalidCiphertextReturnsNull() {
byte[] input = getRandomBytes(1234); byte[] input = TestUtils.getRandomBytes(1234);
String password = "password"; String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password, null); byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
// Modify the ciphertext
// Modify the format version int position = new Random().nextInt(ciphertext.length);
ciphertext[0] ^= (byte) 0xFF; ciphertext[position] = (byte) (ciphertext[position] ^ 0xFF);
try { byte[] output = crypto.decryptWithPassword(ciphertext, password, null);
crypto.decryptWithPassword(ciphertext, password, null); assertNull(output);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
}
@Test
public void testInvalidPasswordThrowsException() {
byte[] input = getRandomBytes(1234);
byte[] ciphertext = crypto.encryptWithPassword(input, "password", null);
// Try to decrypt with the wrong password
try {
crypto.decryptWithPassword(ciphertext, "wrong", null);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
}
@Test
public void testMissingKeyStrengthenerThrowsException() {
SecretKey strengthened = getSecretKey();
context.checking(new Expectations() {{
oneOf(keyStrengthener).strengthenKey(with(any(SecretKey.class)));
will(returnValue(strengthened));
}});
// Use the key strengthener during encryption
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext =
crypto.encryptWithPassword(input, password, keyStrengthener);
// The key strengthener is missing during decryption
try {
crypto.decryptWithPassword(ciphertext, password, null);
fail();
} catch (DecryptionException expected) {
assertEquals(KEY_STRENGTHENER_ERROR, expected.getDecryptionResult());
}
}
@Test
public void testKeyStrengthenerFailureThrowsException() {
SecretKey strengthened = getSecretKey();
context.checking(new Expectations() {{
oneOf(keyStrengthener).strengthenKey(with(any(SecretKey.class)));
will(returnValue(strengthened));
oneOf(keyStrengthener).isInitialised();
will(returnValue(false));
}});
// Use the key strengthener during encryption
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext =
crypto.encryptWithPassword(input, password, keyStrengthener);
// The key strengthener fails during decryption
try {
crypto.decryptWithPassword(ciphertext, password, keyStrengthener);
fail();
} catch (DecryptionException expected) {
assertEquals(KEY_STRENGTHENER_ERROR, expected.getDecryptionResult());
}
} }
} }

View File

@@ -7,20 +7,18 @@ import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -32,9 +30,6 @@ import static org.junit.Assert.fail;
public class ConnectionRegistryImplTest extends BrambleMockTestCase { public class ConnectionRegistryImplTest extends BrambleMockTestCase {
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final Clock clock = context.mock(Clock.class);
private final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final ContactId contactId1 = getContactId(); private final ContactId contactId1 = getContactId();
@@ -45,25 +40,17 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
@Test @Test
public void testRegisterAndUnregister() { public void testRegisterAndUnregister() {
context.checking(new Expectations() {{ ConnectionRegistry c = new ConnectionRegistryImpl(eventBus);
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(10_000L), with(10_000L), with(MILLISECONDS));
}});
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus, clock,
scheduler);
context.assertIsSatisfied();
// The registry should be empty // The registry should be empty
assertEquals(emptyList(), c.getConnectedContacts(transportId)); assertEquals(emptyList(), c.getConnectedContacts(transportId));
assertEquals(emptyList(), c.getConnectedContacts(transportId1)); assertEquals(emptyList(), c.getConnectedContacts(transportId1));
// Check that a registered connection shows up - this should // Check that a registered connection shows up - this should
// broadcast a ConnectionOpenedEvent and a ConnectionStatusChangedEvent // broadcast a ConnectionOpenedEvent and a ContactConnectedEvent
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(ConnectionOpenedEvent.class))); oneOf(eventBus).broadcast(with(any(ConnectionOpenedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(ContactConnectedEvent.class)));
ConnectionStatusChangedEvent.class)));
}}); }});
c.registerConnection(contactId, transportId, true); c.registerConnection(contactId, transportId, true);
assertEquals(singletonList(contactId), assertEquals(singletonList(contactId),
@@ -94,13 +81,11 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
// Unregister the other connection - this should broadcast a // Unregister the other connection - this should broadcast a
// ConnectionClosedEvent and a ConnectionStatusChangedEvent // ConnectionClosedEvent and a ContactDisconnectedEvent
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(System.currentTimeMillis()));
oneOf(eventBus).broadcast(with(any(ConnectionClosedEvent.class))); oneOf(eventBus).broadcast(with(any(ConnectionClosedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
ConnectionStatusChangedEvent.class))); ContactDisconnectedEvent.class)));
}}); }});
c.unregisterConnection(contactId, transportId, true); c.unregisterConnection(contactId, transportId, true);
assertEquals(emptyList(), c.getConnectedContacts(transportId)); assertEquals(emptyList(), c.getConnectedContacts(transportId));
@@ -117,12 +102,12 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
// Register both contacts with one transport, one contact with both - // Register both contacts with one transport, one contact with both -
// this should broadcast three ConnectionOpenedEvents and two // this should broadcast three ConnectionOpenedEvents and two
// ConnectionStatusChangedEvents // ContactConnectedEvents
context.checking(new Expectations() {{ context.checking(new Expectations() {{
exactly(3).of(eventBus).broadcast(with(any( exactly(3).of(eventBus).broadcast(with(any(
ConnectionOpenedEvent.class))); ConnectionOpenedEvent.class)));
exactly(2).of(eventBus).broadcast(with(any( exactly(2).of(eventBus).broadcast(with(any(
ConnectionStatusChangedEvent.class))); ContactConnectedEvent.class)));
}}); }});
c.registerConnection(contactId, transportId, true); c.registerConnection(contactId, transportId, true);
c.registerConnection(contactId1, transportId, true); c.registerConnection(contactId1, transportId, true);
@@ -137,14 +122,7 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
@Test @Test
public void testRegisterAndUnregisterPendingContacts() { public void testRegisterAndUnregisterPendingContacts() {
context.checking(new Expectations() {{ ConnectionRegistry c = new ConnectionRegistryImpl(eventBus);
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(10_000L), with(10_000L), with(MILLISECONDS));
}});
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus, clock,
scheduler);
context.assertIsSatisfied();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(

View File

@@ -13,8 +13,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
@@ -322,7 +322,7 @@ public class PollerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testPollsOnTransportEnabled() throws Exception { public void testPollsOnTransportActivated() throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class); DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -361,7 +361,7 @@ public class PollerImplTest extends BrambleMockTestCase {
pairOf(equal(properties), any(ConnectionHandler.class))))); pairOf(equal(properties), any(ConnectionHandler.class)))));
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
} }
@Test @Test
@@ -402,11 +402,11 @@ public class PollerImplTest extends BrambleMockTestCase {
// All contacts are connected, so don't poll the plugin // All contacts are connected, so don't poll the plugin
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
} }
@Test @Test
public void testCancelsPollingOnTransportDisabled() { public void testCancelsPollingOnTransportDeactivated() {
Plugin plugin = context.mock(Plugin.class); Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -424,11 +424,11 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L), oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
with(MILLISECONDS)); with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// The plugin is disabled before the task runs - cancel the task // The plugin is deactivated before the task runs - cancel the task
oneOf(future).cancel(false); oneOf(future).cancel(false);
}}); }});
poller.eventOccurred(new TransportEnabledEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
poller.eventOccurred(new TransportDisabledEvent(transportId)); poller.eventOccurred(new TransportInactiveEvent(transportId));
} }
} }

View File

@@ -4,14 +4,16 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
@@ -21,6 +23,7 @@ import java.net.InetSocketAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -31,89 +34,57 @@ import static java.util.concurrent.Executors.newCachedThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.plugin.tcp.LanTcpPlugin.areAddressesInSameNetwork; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class LanTcpPluginTest extends BrambleTestCase { public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff(); private final Backoff backoff = new TestBackoff();
private final ExecutorService ioExecutor = newCachedThreadPool(); private final ExecutorService ioExecutor = newCachedThreadPool();
private Callback callback = null;
private LanTcpPlugin plugin = null;
@Before
public void setUp() {
callback = new Callback();
plugin = new LanTcpPlugin(ioExecutor, backoff, callback, 0, 0, 1000) {
@Override
protected boolean canConnectToOwnAddress() {
return true;
}
};
}
@Test @Test
public void testAreAddressesInSameNetwork() { public void testAddressesAreOnSameLan() {
// Local and remote in 10.0.0.0/8 Callback callback = new Callback();
assertTrue(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
makeAddress(10, 255, 255, 255), 8)); 0, 0);
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), // Local and remote in 10.0.0.0/8 should return true
makeAddress(10, 255, 255, 255), 9)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255)));
// Local and remote in 172.16.0.0/12 // Local and remote in 172.16.0.0/12 should return true
assertTrue(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), assertTrue(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255), 12)); makeAddress(172, 31, 255, 255)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), // Local and remote in 192.168.0.0/16 should return true
makeAddress(172, 31, 255, 255), 13)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local and remote in 192.168.0.0/16 // Local and remote in 169.254.0.0/16 (link-local) should return true
assertTrue(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0), assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
makeAddress(192, 168, 255, 255), 16)); makeAddress(169, 254, 255, 255)));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0), // Local and remote in different recognised prefixes should return false
makeAddress(192, 168, 255, 255), 17)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(172, 31, 255, 255)));
// Local and remote in 169.254.0.0/16 assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
assertTrue(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0), makeAddress(192, 168, 255, 255)));
makeAddress(169, 254, 255, 255), 16)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0), makeAddress(10, 255, 255, 255)));
makeAddress(169, 254, 255, 255), 17)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local in 10.0.0.0/8, remote in a different network assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), makeAddress(10, 255, 255, 255)));
makeAddress(172, 31, 255, 255), 8)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), makeAddress(172, 31, 255, 255)));
makeAddress(192, 168, 255, 255), 8)); // Remote prefix unrecognised should return false
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(169, 254, 255, 255), 8)); makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
// Local in 172.16.0.0/12, remote in a different network makeAddress(1, 2, 3, 4)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255), 12)); makeAddress(1, 2, 3, 4)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), // Both prefixes unrecognised should return true (could be link-local)
makeAddress(192, 168, 255, 255), 12)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(1, 2, 3, 4),
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), makeAddress(5, 6, 7, 8)));
makeAddress(169, 254, 255, 255), 12));
// Local in 192.168.0.0/16, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(172, 31, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(169, 254, 255, 255), 16));
// Local in 169.254.0.0/16, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(10, 255, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(172, 31, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(192, 168, 255, 255), 16));
} }
private byte[] makeAddress(int... parts) { private byte[] makeAddress(int... parts) {
@@ -124,7 +95,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testIncomingConnection() throws Exception { public void testIncomingConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -153,7 +130,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testOutgoingConnection() throws Exception { public void testOutgoingConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -196,7 +179,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testIncomingKeyAgreementConnection() throws Exception { public void testIncomingKeyAgreementConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
KeyAgreementListener kal = KeyAgreementListener kal =
@@ -238,7 +227,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testOutgoingKeyAgreementConnection() throws Exception { public void testOutgoingKeyAgreementConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -283,12 +278,63 @@ public class LanTcpPluginTest extends BrambleTestCase {
plugin.stop(); plugin.stop();
} }
@Test
public void testComparatorPrefersNonZeroPorts() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
assertEquals(0, comparator.compare(nonZero, nonZero));
assertTrue(comparator.compare(nonZero, zero) < 0);
assertTrue(comparator.compare(zero, nonZero) > 0);
assertEquals(0, comparator.compare(zero, zero));
}
@Test
public void testComparatorPrefersLongerPrefixes() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
assertEquals(0, comparator.compare(prefix192, prefix192));
assertTrue(comparator.compare(prefix192, prefix172) < 0);
assertTrue(comparator.compare(prefix192, prefix10) < 0);
assertTrue(comparator.compare(prefix172, prefix192) > 0);
assertEquals(0, comparator.compare(prefix172, prefix172));
assertTrue(comparator.compare(prefix172, prefix10) < 0);
assertTrue(comparator.compare(prefix10, prefix192) > 0);
assertTrue(comparator.compare(prefix10, prefix172) > 0);
assertEquals(0, comparator.compare(prefix10, prefix10));
}
@Test
public void testComparatorPrefersSiteLocalToLinkLocal() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0);
assertTrue(comparator.compare(prefix192, linkLocal) < 0);
assertTrue(comparator.compare(prefix172, linkLocal) < 0);
assertTrue(comparator.compare(prefix10, linkLocal) < 0);
assertTrue(comparator.compare(linkLocal, prefix192) > 0);
assertTrue(comparator.compare(linkLocal, prefix172) > 0);
assertTrue(comparator.compare(linkLocal, prefix10) > 0);
assertEquals(0, comparator.compare(linkLocal, linkLocal));
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean systemHasLocalIpv4Address() throws Exception { private boolean systemHasLocalIpv4Address() throws Exception {
for (NetworkInterface i : list(getNetworkInterfaces())) { for (NetworkInterface i : list(getNetworkInterfaces())) {
for (InetAddress a : list(i.getInetAddresses())) { for (InetAddress a : list(i.getInetAddresses())) {
if (a instanceof Inet4Address) { if (a instanceof Inet4Address)
return a.isLinkLocalAddress() || a.isSiteLocalAddress(); return a.isLinkLocalAddress() || a.isSiteLocalAddress();
}
} }
} }
return false; return false;
@@ -297,15 +343,18 @@ public class LanTcpPluginTest extends BrambleTestCase {
@NotNullByDefault @NotNullByDefault
private static class Callback implements PluginCallback { private static class Callback implements PluginCallback {
// Properties will be stored twice: the preferred port at startup, private final CountDownLatch propertiesLatch = new CountDownLatch(1);
// and the IP:port when the server socket is bound
private final CountDownLatch propertiesLatch = new CountDownLatch(2);
private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1);
private final TransportProperties local = new TransportProperties(); private final TransportProperties local = new TransportProperties();
private final Settings settings = new Settings();
private Callback() {
settings.putBoolean(PREF_PLUGIN_ENABLE, true);
}
@Override @Override
public Settings getSettings() { public Settings getSettings() {
return new Settings(); return settings;
} }
@Override @Override
@@ -324,11 +373,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State newState) {
}
@Override
public void transportDisabled() {
} }
@Override @Override

View File

@@ -24,18 +24,14 @@ import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.GROUP_KEY_DISCOVERED;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -190,25 +186,25 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Message message = getMessage(contactGroupId); Message message = getMessage(contactGroupId);
Metadata meta = new Metadata(); Metadata meta = new Metadata();
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 2), new BdfEntry("version", 2),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
); );
Map<MessageId, BdfDictionary> messageMetadata = Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
// A remote update for another transport should be ignored // A remote update for another transport should be ignored
MessageId barUpdateId = new MessageId(getRandomId()); MessageId barUpdateId = new MessageId(getRandomId());
messageMetadata.put(barUpdateId, BdfDictionary.of( messageMetadata.put(barUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "bar"), new BdfEntry("transportId", "bar"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
)); ));
// A local update for the same transport should be ignored // A local update for the same transport should be ignored
MessageId localUpdateId = new MessageId(getRandomId()); MessageId localUpdateId = new MessageId(getRandomId());
messageMetadata.put(localUpdateId, BdfDictionary.of( messageMetadata.put(localUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -232,18 +228,18 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Metadata meta = new Metadata(); Metadata meta = new Metadata();
// Version 4 is being delivered // Version 4 is being delivered
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 4), new BdfEntry("version", 4),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
); );
Map<MessageId, BdfDictionary> messageMetadata = Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
// An older remote update for the same transport should be deleted // An older remote update for the same transport should be deleted
MessageId fooVersion3 = new MessageId(getRandomId()); MessageId fooVersion3 = new MessageId(getRandomId());
messageMetadata.put(fooVersion3, BdfDictionary.of( messageMetadata.put(fooVersion3, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 3), new BdfEntry("version", 3),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
)); ));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -269,18 +265,18 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Metadata meta = new Metadata(); Metadata meta = new Metadata();
// Version 3 is being delivered // Version 3 is being delivered
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 3), new BdfEntry("version", 3),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
); );
Map<MessageId, BdfDictionary> messageMetadata = Map<MessageId, BdfDictionary> messageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
// A newer remote update for the same transport should not be deleted // A newer remote update for the same transport should not be deleted
MessageId fooVersion4 = new MessageId(getRandomId()); MessageId fooVersion4 = new MessageId(getRandomId());
messageMetadata.put(fooVersion4, BdfDictionary.of( messageMetadata.put(fooVersion4, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 4), new BdfEntry("version", 4),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
)); ));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -346,9 +342,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// A local update for another transport should be ignored // A local update for another transport should be ignored
MessageId barUpdateId = new MessageId(getRandomId()); MessageId barUpdateId = new MessageId(getRandomId());
messageMetadata.put(barUpdateId, BdfDictionary.of( messageMetadata.put(barUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "bar"), new BdfEntry("transportId", "bar"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
@@ -370,16 +366,16 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// A local update for another transport should be ignored // A local update for another transport should be ignored
MessageId barUpdateId = new MessageId(getRandomId()); MessageId barUpdateId = new MessageId(getRandomId());
messageMetadata.put(barUpdateId, BdfDictionary.of( messageMetadata.put(barUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "bar"), new BdfEntry("transportId", "bar"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
// A local update for the right transport should be returned // A local update for the right transport should be returned
MessageId fooUpdateId = new MessageId(getRandomId()); MessageId fooUpdateId = new MessageId(getRandomId());
messageMetadata.put(fooUpdateId, BdfDictionary.of( messageMetadata.put(fooUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict); BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
@@ -409,28 +405,28 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
List<Contact> contacts = asList(contact1, contact2); List<Contact> contacts = asList(contact1, contact2);
Group contactGroup1 = getGroup(CLIENT_ID, MAJOR_VERSION); Group contactGroup1 = getGroup(CLIENT_ID, MAJOR_VERSION);
Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION); Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata = Map<MessageId, BdfDictionary> messageMetadata2 =
new LinkedHashMap<>(); new LinkedHashMap<>();
// A remote update for another transport should be ignored // A remote update for another transport should be ignored
MessageId barUpdateId = new MessageId(getRandomId()); MessageId barUpdateId = new MessageId(getRandomId());
messageMetadata.put(barUpdateId, BdfDictionary.of( messageMetadata2.put(barUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "bar"), new BdfEntry("transportId", "bar"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
)); ));
// A local update for the right transport should be ignored // A local update for the right transport should be ignored
MessageId localUpdateId = new MessageId(getRandomId()); MessageId localUpdateId = new MessageId(getRandomId());
messageMetadata.put(localUpdateId, BdfDictionary.of( messageMetadata2.put(localUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
// A remote update for the right transport should be returned // A remote update for the right transport should be returned
MessageId fooUpdateId = new MessageId(getRandomId()); MessageId fooUpdateId = new MessageId(getRandomId());
messageMetadata.put(fooUpdateId, BdfDictionary.of( messageMetadata2.put(fooUpdateId, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry("local", false)
)); ));
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict); BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
@@ -444,25 +440,19 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
will(returnValue(contactGroup1)); will(returnValue(contactGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup1.getId()); contactGroup1.getId());
will(returnValue(emptyMap())); will(returnValue(Collections.emptyMap()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup1.getId());
will(returnValue(new BdfDictionary()));
// Second contact: returns an update // Second contact: returns an update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact2); MAJOR_VERSION, contact2);
will(returnValue(contactGroup2)); will(returnValue(contactGroup2));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup2.getId()); contactGroup2.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata2));
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId); oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
will(returnValue(fooUpdate)); will(returnValue(fooUpdate));
oneOf(clientHelper).parseAndValidateTransportProperties( oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict); fooPropertiesDict);
will(returnValue(fooProperties)); will(returnValue(fooProperties));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup2.getId());
will(returnValue(new BdfDictionary()));
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
@@ -473,62 +463,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
assertEquals(fooProperties, properties.get(contact2.getId())); assertEquals(fooProperties, properties.get(contact2.getId()));
} }
@Test
public void testReceivePropertiesOverrideDiscoveredProperties()
throws Exception {
Transaction txn = new Transaction(null, true);
Contact contact = getContact();
List<Contact> contacts = singletonList(contact);
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
MessageId updateId = new MessageId(getRandomId());
Map<MessageId, BdfDictionary> messageMetadata = singletonMap(updateId,
BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"),
new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, false)
));
BdfList update = BdfList.of("foo", 1, fooPropertiesDict);
TransportProperties discovered = new TransportProperties();
discovered.put("fooKey1", "overridden");
discovered.put("fooKey3", "fooValue3");
BdfDictionary discoveredDict = new BdfDictionary(discovered);
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_DISCOVERED, discoveredDict)
);
TransportProperties merged = new TransportProperties();
merged.putAll(fooProperties);
merged.put("fooKey3", "fooValue3");
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContacts(txn);
will(returnValue(contacts));
// One update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, updateId);
will(returnValue(update));
oneOf(clientHelper).parseAndValidateTransportProperties(
fooPropertiesDict);
will(returnValue(fooProperties));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(clientHelper).parseAndValidateTransportProperties(
discoveredDict);
will(returnValue(discovered));
}});
TransportPropertyManagerImpl t = createInstance();
Map<ContactId, TransportProperties> properties =
t.getRemoteProperties(new TransportId("foo"));
assertEquals(merged, properties.get(contact.getId()));
}
@Test @Test
public void testMergingUnchangedPropertiesDoesNotCreateUpdate() public void testMergingUnchangedPropertiesDoesNotCreateUpdate()
throws Exception { throws Exception {
@@ -536,9 +470,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
MessageId updateId = new MessageId(getRandomId()); MessageId updateId = new MessageId(getRandomId());
Map<MessageId, BdfDictionary> messageMetadata = singletonMap(updateId, Map<MessageId, BdfDictionary> messageMetadata = singletonMap(updateId,
BdfDictionary.of( BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
)); ));
BdfList update = BdfList.of("foo", 1, fooPropertiesDict); BdfList update = BdfList.of("foo", 1, fooPropertiesDict);
@@ -571,7 +505,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// There are no existing properties to merge with // There are no existing properties to merge with
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
localGroup.getId()); localGroup.getId());
will(returnValue(emptyMap())); will(returnValue(Collections.emptyMap()));
// Store the new properties in the local group, version 1 // Store the new properties in the local group, version 1
expectStoreMessage(txn, localGroup.getId(), "foo", expectStoreMessage(txn, localGroup.getId(), "foo",
fooPropertiesDict, 1, true, false); fooPropertiesDict, 1, true, false);
@@ -583,7 +517,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId()); contactGroup.getId());
will(returnValue(emptyMap())); will(returnValue(Collections.emptyMap()));
expectStoreMessage(txn, contactGroup.getId(), "foo", expectStoreMessage(txn, contactGroup.getId(), "foo",
fooPropertiesDict, 1, true, true); fooPropertiesDict, 1, true, true);
}}); }});
@@ -598,9 +532,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Contact contact = getContact(); Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary oldMetadata = BdfDictionary.of( BdfDictionary oldMetadata = BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry("version", 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry("local", true)
); );
MessageId localGroupUpdateId = new MessageId(getRandomId()); MessageId localGroupUpdateId = new MessageId(getRandomId());
Map<MessageId, BdfDictionary> localGroupMessageMetadata = Map<MessageId, BdfDictionary> localGroupMessageMetadata =
@@ -655,14 +589,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// The latest update for transport "foo" should be returned // The latest update for transport "foo" should be returned
MessageId fooVersion999 = new MessageId(getRandomId()); MessageId fooVersion999 = new MessageId(getRandomId());
messageMetadata.put(fooVersion999, BdfDictionary.of( messageMetadata.put(fooVersion999, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry(MSG_KEY_VERSION, 999) new BdfEntry("version", 999)
)); ));
// The latest update for transport "bar" should be returned // The latest update for transport "bar" should be returned
MessageId barVersion3 = new MessageId(getRandomId()); MessageId barVersion3 = new MessageId(getRandomId());
messageMetadata.put(barVersion3, BdfDictionary.of( messageMetadata.put(barVersion3, BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, "bar"), new BdfEntry("transportId", "bar"),
new BdfEntry(MSG_KEY_VERSION, 3) new BdfEntry("version", 3)
)); ));
BdfList fooUpdate = BdfList.of("foo", 999, fooPropertiesDict); BdfList fooUpdate = BdfList.of("foo", 999, fooPropertiesDict);
BdfList barUpdate = BdfList.of("bar", 3, barPropertiesDict); BdfList barUpdate = BdfList.of("bar", 3, barPropertiesDict);
@@ -693,9 +627,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Message message = getMessage(g); Message message = getMessage(g);
long timestamp = message.getTimestamp(); long timestamp = message.getTimestamp();
BdfDictionary meta = BdfDictionary.of( BdfDictionary meta = BdfDictionary.of(
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId), new BdfEntry("transportId", transportId),
new BdfEntry(MSG_KEY_VERSION, version), new BdfEntry("version", version),
new BdfEntry(MSG_KEY_LOCAL, local) new BdfEntry("local", local)
); );
context.checking(new Expectations() {{ context.checking(new Expectations() {{

View File

@@ -17,8 +17,8 @@ import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -178,10 +178,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.startService(); rendezvousPoller.startService();
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - no endpoints should be created yet // Activate the transport - no endpoints should be created yet
expectGetPlugin(); expectGetPlugin();
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
@@ -212,8 +212,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactRemovedEvent(pendingContact.getId())); new PendingContactRemovedEvent(pendingContact.getId()));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint is already closed // Deactivate the transport - endpoint is already closed
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
} }
@Test @Test
@@ -230,10 +230,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.startService(); rendezvousPoller.startService();
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - no endpoints should be created yet // Activate the transport - no endpoints should be created yet
expectGetPlugin(); expectGetPlugin();
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled // Add the pending contact - endpoint should be created and polled
@@ -269,12 +269,12 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactRemovedEvent(pendingContact.getId())); new PendingContactRemovedEvent(pendingContact.getId()));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint is already closed // Deactivate the transport - endpoint is already closed
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
} }
@Test @Test
public void testCreatesAndClosesEndpointsWhenTransportIsEnabledAndDisabled() public void testCreatesAndClosesEndpointsWhenTransportIsActivatedAndDeactivated()
throws Exception { throws Exception {
long beforeExpiry = pendingContact.getTimestamp(); long beforeExpiry = pendingContact.getTimestamp();
@@ -292,19 +292,19 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new PendingContactAddedEvent(pendingContact)); new PendingContactAddedEvent(pendingContact));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Enable the transport - endpoint should be created // Activate the transport - endpoint should be created
expectGetPlugin(); expectGetPlugin();
expectCreateEndpoint(); expectCreateEndpoint();
expectStateChangedEvent(WAITING_FOR_CONNECTION); expectStateChangedEvent(WAITING_FOR_CONNECTION);
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Disable the transport - endpoint should be closed // Deactivate the transport - endpoint should be closed
expectCloseEndpoint(); expectCloseEndpoint();
expectStateChangedEvent(OFFLINE); expectStateChangedEvent(OFFLINE);
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId));
context.assertIsSatisfied(); context.assertIsSatisfied();
// Remove the pending contact - endpoint is already closed // Remove the pending contact - endpoint is already closed

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -38,11 +37,6 @@ public class TestDuplexTransportConnection
return writer; return writer;
} }
@Override
public TransportProperties getRemoteProperties() {
return new TransportProperties();
}
/** /**
* Creates and returns a pair of TestDuplexTransportConnections that are * Creates and returns a pair of TestDuplexTransportConnections that are
* connected to each other. * connected to each other.

View File

@@ -16,7 +16,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2' implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2' implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.5.10@zip' tor 'org.briarproject:tor:0.3.5.9@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip' tor 'org.briarproject:obfs4proxy:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -37,9 +37,9 @@ public class DesktopPluginModule extends PluginModule {
backoffFactory); backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory); reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, eventBus,
backoffFactory); backoffFactory);
DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor, eventBus,
backoffFactory, shutdownManager); backoffFactory, shutdownManager);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
asList(bluetooth, modem, lan, wan); asList(bluetooth, modem, lan, wan);

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
@@ -23,9 +24,16 @@ import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -44,8 +52,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private final PluginCallback callback; private final PluginCallback callback;
private final int maxLatency; private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private final PluginState state = new PluginState();
private volatile boolean running = false;
private volatile Modem modem = null; private volatile Modem modem = null;
ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList, ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
@@ -75,6 +83,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
for (String portName : serialPortList.getPortNames()) { for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName); LOG.info("Trying to initialise modem on " + portName);
@@ -83,18 +92,20 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue; if (!modem.start()) continue;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName); LOG.info("Initialised modem on " + portName);
running = true; state.setInitialised();
return; return;
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
LOG.warning("Failed to initialised modem");
state.setFailed();
throw new PluginException(); throw new PluginException();
} }
@Override @Override
public void stop() { public void stop() {
running = false; state.setStopped();
if (modem != null) { if (modem != null) {
try { try {
modem.stop(); modem.stop();
@@ -105,8 +116,13 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
@Override @Override
public boolean isRunning() { public State getState() {
return running; return state.getState();
}
@Override
public int getReasonDisabled() {
return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
} }
@Override @Override
@@ -125,8 +141,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
private boolean resetModem() { private void resetModem() {
if (!running) return false; if (getState() != ACTIVE) return;
for (String portName : serialPortList.getPortNames()) { for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName); LOG.info("Trying to initialise modem on " + portName);
@@ -135,18 +151,18 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue; if (!modem.start()) continue;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName); LOG.info("Initialised modem on " + portName);
return true; return;
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
running = false; LOG.warning("Failed to initialise modem");
return false; state.setFailed();
} }
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
if (!running) return null; if (getState() != ACTIVE) return null;
// Get the ISO 3166 code for the caller's country // Get the ISO 3166 code for the caller's country
String fromIso = callback.getLocalProperties().get("iso3166"); String fromIso = callback.getLocalProperties().get("iso3166");
if (isNullOrEmpty(fromIso)) return null; if (isNullOrEmpty(fromIso)) return null;
@@ -232,4 +248,41 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (exception) resetModem(); if (exception) resetModem();
} }
} }
@ThreadSafe
@NotNullByDefault
private class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
initialised = false,
failed = false;
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
private synchronized void setStopped() {
stopped = true;
callback.pluginStateChanged(getState());
}
private synchronized void setInitialised() {
initialised = true;
callback.pluginStateChanged(getState());
}
private synchronized void setFailed() {
failed = true;
callback.pluginStateChanged(getState());
}
private State getState() {
if (!started || stopped) return DISABLED;
if (failed) return INACTIVE;
return initialised ? ACTIVE : ENABLING;
}
}
} }

View File

@@ -9,6 +9,8 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@@ -33,6 +35,7 @@ public class ModemPluginTest extends BrambleMockTestCase {
@Test @Test
public void testModemCreation() throws Exception { public void testModemCreation() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo", "bar", "baz"})); will(returnValue(new String[] {"foo", "bar", "baz"}));
// First call to createModem() returns false // First call to createModem() returns false
@@ -50,6 +53,7 @@ public class ModemPluginTest extends BrambleMockTestCase {
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
}}); }});
plugin.start(); plugin.start();
@@ -65,12 +69,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));
@@ -93,12 +99,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));
@@ -121,12 +129,14 @@ public class ModemPluginTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// start() // start()
oneOf(callback).pluginStateChanged(ENABLING);
oneOf(serialPortList).getPortNames(); oneOf(serialPortList).getPortNames();
will(returnValue(new String[] {"foo"})); will(returnValue(new String[] {"foo"}));
oneOf(modemFactory).createModem(plugin, "foo"); oneOf(modemFactory).createModem(plugin, "foo");
will(returnValue(modem)); will(returnValue(modem));
oneOf(modem).start(); oneOf(modem).start();
will(returnValue(true)); will(returnValue(true));
oneOf(callback).pluginStateChanged(ACTIVE);
// createConnection() // createConnection()
oneOf(callback).getLocalProperties(); oneOf(callback).getLocalProperties();
will(returnValue(local)); will(returnValue(local));

View File

@@ -32,6 +32,7 @@ import javax.net.SocketFactory;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled; import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
@@ -141,10 +142,10 @@ public class BridgeTest extends BrambleTestCase {
plugin.start(); plugin.start();
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
while (clock.currentTimeMillis() - start < TIMEOUT) { while (clock.currentTimeMillis() - start < TIMEOUT) {
if (plugin.isRunning()) return; if (plugin.getState() == ACTIVE) return;
clock.sleep(500); clock.sleep(500);
} }
if (!plugin.isRunning()) { if (plugin.getState() != ACTIVE) {
fail("Could not connect to Tor within timeout."); fail("Could not connect to Tor within timeout.");
} }
} finally { } finally {

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin.tor; package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -30,11 +31,7 @@ public class TestPluginCallback implements PluginCallback {
} }
@Override @Override
public void transportEnabled() { public void pluginStateChanged(State state) {
}
@Override
public void transportDisabled() {
} }
@Override @Override

View File

@@ -24,7 +24,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642', 'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.10:tor-0.3.5.10.zip:7b387d3523ae8af289c23be59dc4c64ec5d3721385d7825a09705095e3318d5c', 'org.briarproject:tor:0.3.5.9:tor-0.3.5.9.zip:6c3994b129db019cc23caaf50d6b4383903c40d05fbc47fc94211170a3e5d38c',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@+id/end"
app:constraintSetStart="@id/start"
app:duration="1000">
<OnClick app:targetId="@+id/chevronView" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/longRangeLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0">
<CustomAttribute
app:attributeName="crossfade"
app:customFloatValue="0" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/connectionsLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1.0">
<CustomAttribute
app:attributeName="crossfade"
app:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/backgroundView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/torSwitch"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/nearbyLabel"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/wifiSwitch"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/btSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/btSwitch"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/longRangeLabel"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/torSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/nearbyLabel"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/wifiSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/torIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/wifiIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/btIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/connectionsLabel"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/torIcon"
app:layout_constraintEnd_toStartOf="@+id/torIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/torIcon" />
</ConstraintSet>
</MotionScene>

View File

@@ -22,8 +22,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 10207 versionCode 10205
versionName "1.2.7" versionName "1.2.5"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -98,7 +98,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-beta01' implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01' implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
implementation 'ch.acra:acra:4.11' implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'

View File

@@ -28,9 +28,7 @@ import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@RequiresApi(23) @RequiresApi(23)
@NotNullByDefault @NotNullByDefault
@@ -81,10 +79,7 @@ class AndroidKeyStrengthener implements KeyStrengthener {
return true; return true;
} }
return false; return false;
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException | IOException e) {
logException(LOG, WARNING, e);
return false;
} catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View File

@@ -36,7 +36,7 @@ import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -66,8 +66,8 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = { @Module(includes = {
NavDrawerModule.class,
ContactExchangeModule.class, ContactExchangeModule.class,
LoginModule.class,
ViewModelModule.class ViewModelModule.class
}) })
public class AppModule { public class AppModule {

View File

@@ -8,8 +8,8 @@ import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.navdrawer.NavDrawerController; import org.briarproject.briar.android.login.ChangePasswordController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl; import org.briarproject.briar.android.login.ChangePasswordControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -44,6 +44,13 @@ public class ActivityModule {
return setupController; return setupController;
} }
@ActivityScope
@Provides
ChangePasswordController providePasswordController(
ChangePasswordControllerImpl passwordController) {
return passwordController;
}
@ActivityScope @ActivityScope
@Provides @Provides
protected BriarController provideBriarController( protected BriarController provideBriarController(
@@ -58,17 +65,10 @@ public class ActivityModule {
return dbController; return dbController;
} }
@ActivityScope
@Provides
NavDrawerController provideNavDrawerController(
NavDrawerControllerImpl navDrawerController) {
activity.addLifecycleController(navDrawerController);
return navDrawerController;
}
@ActivityScope @ActivityScope
@Provides @Provides
BriarServiceConnection provideBriarServiceConnection() { BriarServiceConnection provideBriarServiceConnection() {
return new BriarServiceConnection(); return new BriarServiceConnection();
} }
} }

View File

@@ -92,9 +92,6 @@ public abstract class BaseActivity extends AppCompatActivity
.build(); .build();
injectActivity(activityComponent); injectActivity(activityComponent);
super.onCreate(state); super.onCreate(state);
if (LOG.isLoggable(INFO)) {
LOG.info("Creating " + getClass().getSimpleName());
}
// WARNING: When removing this or making it possible to turn it off, // WARNING: When removing this or making it possible to turn it off,
// we need a solution for the app lock feature. // we need a solution for the app lock feature.
@@ -130,9 +127,8 @@ public abstract class BaseActivity extends AppCompatActivity
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO))
LOG.info("Starting " + getClass().getSimpleName()); LOG.info("Starting " + this.getClass().getSimpleName());
}
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityStart(); alc.onActivityStart();
} }
@@ -148,28 +144,11 @@ public abstract class BaseActivity extends AppCompatActivity
return (ScreenFilterDialogFragment) f; return (ScreenFilterDialogFragment) f;
} }
@Override
protected void onResume() {
super.onResume();
if (LOG.isLoggable(INFO)) {
LOG.info("Resuming " + getClass().getSimpleName());
}
}
@Override
protected void onPause() {
super.onPause();
if (LOG.isLoggable(INFO)) {
LOG.info("Pausing " + getClass().getSimpleName());
}
}
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO))
LOG.info("Stopping " + getClass().getSimpleName()); LOG.info("Stopping " + this.getClass().getSimpleName());
}
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityStop(); alc.onActivityStop();
} }
@@ -224,9 +203,6 @@ public abstract class BaseActivity extends AppCompatActivity
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
if (LOG.isLoggable(INFO)) {
LOG.info("Destroying " + getClass().getSimpleName());
}
destroyed = true; destroyed = true;
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityDestroy(); alc.onActivityDestroy();

View File

@@ -95,14 +95,12 @@ public abstract class BriarActivity extends BaseActivity {
// Also check that the activity isn't finishing already. // Also check that the activity isn't finishing already.
// This is possible if we finished in onActivityResult(). // This is possible if we finished in onActivityResult().
// Launching another StartupActivity would cause a loop. // Launching another StartupActivity would cause a loop.
LOG.info("Not signed in, launching StartupActivity");
Intent i = new Intent(this, StartupActivity.class); Intent i = new Intent(this, StartupActivity.class);
startActivityForResult(i, REQUEST_PASSWORD); startActivityForResult(i, REQUEST_PASSWORD);
} else if (lockManager.isLocked() && !isFinishing()) { } else if (lockManager.isLocked() && !isFinishing()) {
// Also check that the activity isn't finishing already. // Also check that the activity isn't finishing already.
// This is possible if we finished in onActivityResult(). // This is possible if we finished in onActivityResult().
// Launching another UnlockActivity would cause a loop. // Launching another UnlockActivity would cause a loop.
LOG.info("Locked, launching UnlockActivity");
Intent i = new Intent(this, UnlockActivity.class); Intent i = new Intent(this, UnlockActivity.class);
startActivityForResult(i, REQUEST_UNLOCK); startActivityForResult(i, REQUEST_UNLOCK);
} else if (SDK_INT >= 23) { } else if (SDK_INT >= 23) {

View File

@@ -9,6 +9,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import java.util.Collection; import java.util.Collection;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
@@ -17,10 +18,7 @@ public interface BlogController extends BaseController {
void setGroupId(GroupId g); void setGroupId(GroupId g);
@UiThread @UiThread
void setBlogSharingListener(BlogSharingListener listener); void setBlogSharingListener(@Nullable BlogSharingListener listener);
@UiThread
void unsetBlogSharingListener(BlogSharingListener listener);
void loadBlogPosts( void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);

View File

@@ -96,15 +96,10 @@ class BlogControllerImpl extends BaseControllerImpl
} }
@Override @Override
public void setBlogSharingListener(BlogSharingListener listener) { public void setBlogSharingListener(@Nullable BlogSharingListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetBlogSharingListener(BlogSharingListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (groupId == null || listener == null) if (groupId == null || listener == null)

View File

@@ -141,8 +141,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
blogController.unsetBlogSharingListener(this); blogController.setBlogSharingListener(null);
sharingController.unsetSharingListener(this);
} }
@Override @Override

View File

@@ -7,6 +7,7 @@ import org.briarproject.briar.api.blog.Blog;
import java.util.Collection; import java.util.Collection;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
@@ -18,10 +19,7 @@ public interface FeedController extends BaseController {
void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler); void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler);
@UiThread @UiThread
void setFeedListener(FeedListener listener); void setFeedListener(@Nullable FeedListener listener);
@UiThread
void unsetFeedListener(FeedListener listener);
@NotNullByDefault @NotNullByDefault
interface FeedListener extends BlogListener { interface FeedListener extends BlogListener {

View File

@@ -69,15 +69,10 @@ class FeedControllerImpl extends BaseControllerImpl implements FeedController {
} }
@Override @Override
public void setFeedListener(FeedListener listener) { public void setFeedListener(@Nullable FeedListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetFeedListener(FeedListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (listener == null) throw new IllegalStateException(); if (listener == null) throw new IllegalStateException();

View File

@@ -134,7 +134,7 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
feedController.unsetFeedListener(this); feedController.setFeedListener(null);
} }
@Override @Override

View File

@@ -2,38 +2,35 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.DISCONNECTED;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
public class ContactItem { public class ContactItem {
private final Contact contact; private final Contact contact;
private ConnectionStatus status; private boolean connected;
public ContactItem(Contact contact) { public ContactItem(Contact contact) {
this(contact, DISCONNECTED); this(contact, false);
} }
public ContactItem(Contact contact, ConnectionStatus status) { public ContactItem(Contact contact, boolean connected) {
this.contact = contact; this.contact = contact;
this.status = status; this.connected = connected;
} }
public Contact getContact() { public Contact getContact() {
return contact; return contact;
} }
ConnectionStatus getConnectionStatus() { boolean isConnected() {
return status; return connected;
} }
void setConnectionStatus(ConnectionStatus status) { void setConnected(boolean connected) {
this.status = status; this.connected = connected;
} }
} }

View File

@@ -7,7 +7,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -17,8 +16,6 @@ import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import im.delight.android.identicons.IdenticonDrawable; import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread @UiThread
@@ -30,7 +27,7 @@ public class ContactItemViewHolder<I extends ContactItem>
protected final ImageView avatar; protected final ImageView avatar;
protected final TextView name; protected final TextView name;
@Nullable @Nullable
private final ImageView bulb; protected final ImageView bulb;
public ContactItemViewHolder(View v) { public ContactItemViewHolder(View v) {
super(v); super(v);
@@ -50,13 +47,10 @@ public class ContactItemViewHolder<I extends ContactItem>
if (bulb != null) { if (bulb != null) {
// online/offline // online/offline
ConnectionStatus status = item.getConnectionStatus(); if (item.isConnected()) {
if (status == CONNECTED) { bulb.setImageResource(R.drawable.contact_connected);
bulb.setImageResource(R.drawable.ic_connected);
} else if (status == RECENTLY_CONNECTED) {
bulb.setImageResource(R.drawable.ic_recently_connected);
} else { } else {
bulb.setImageResource(R.drawable.ic_disconnected); bulb.setImageResource(R.drawable.contact_disconnected);
} }
} }

View File

@@ -39,7 +39,7 @@ public class ContactListAdapter extends
if (c1.getTimestamp() != c2.getTimestamp()) { if (c1.getTimestamp() != c2.getTimestamp()) {
return false; return false;
} }
return c1.getConnectionStatus() == c2.getConnectionStatus(); return c1.isConnected() == c2.isConnected();
} }
@Override @Override

View File

@@ -25,8 +25,8 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -53,6 +53,7 @@ import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial; import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener; import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
@@ -60,7 +61,7 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListen
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName; import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -86,12 +87,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
private ContactListAdapter adapter; private ContactListAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
/** private Snackbar snackbar;
* The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
*/
@Nullable
private Snackbar snackbar = null;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
@@ -136,15 +132,26 @@ public class ContactListFragment extends BaseFragment implements EventListener,
ContactId contactId = item.getContact().getId(); ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt()); i.putExtra(CONTACT_ID, contactId.getInt());
Bundle options = null;
// work-around for android bug #224270
if (SDK_INT >= 23 && !isSamsung7()) { if (SDK_INT >= 23 && !isSamsung7()) {
options = makeTransitionOptions(view); ContactListItemViewHolder holder =
} (ContactListItemViewHolder) list
if (options == null) { .getRecyclerView()
startActivity(i); .findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create(holder.avatar,
getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create(holder.bulb,
getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} else { } else {
ActivityCompat.startActivity(getActivity(), i, options); // work-around for android bug #224270
startActivity(i);
} }
}; };
adapter = new ContactListAdapter(requireContext(), adapter = new ContactListAdapter(requireContext(),
@@ -156,16 +163,14 @@ public class ContactListFragment extends BaseFragment implements EventListener,
list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action)); list.setEmptyAction(getString(R.string.no_contacts_action));
return contentView; snackbar = new BriarSnackbarBuilder()
} .setAction(R.string.show, v ->
startActivity(new Intent(getContext(),
PendingContactListActivity.class)))
.make(contentView, R.string.pending_contact_requests_snackbar,
LENGTH_INDEFINITE);
@Nullable return contentView;
private Bundle makeTransitionOptions(View view) {
View avatar = view.findViewById(R.id.avatarView);
String name = requireNonNull(getTransitionName(avatar));
ActivityOptionsCompat options = makeSceneTransitionAnimation(
requireActivity(), view, name);
return options.toBundle();
} }
@Override @Override
@@ -198,9 +203,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
listener.runOnDbThread(() -> { listener.runOnDbThread(() -> {
try { try {
if (contactManager.getPendingContacts().isEmpty()) { if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar); runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else { } else {
runOnUiThreadUnlessDestroyed(this::showSnackBar); runOnUiThreadUnlessDestroyed(() -> snackbar.show());
} }
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
@@ -215,7 +220,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
adapter.clear(); adapter.clear();
list.showProgressBar(); list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
dismissSnackBar();
} }
private void loadContacts() { private void loadContacts() {
@@ -229,9 +233,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
ContactId id = c.getId(); ContactId id = c.getId();
GroupCount count = GroupCount count =
conversationManager.getGroupCount(id); conversationManager.getGroupCount(id);
ConnectionStatus status = connectionRegistry boolean connected =
.getConnectionStatus(c.getId()); connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, status, count)); contacts.add(new ContactListItem(c, connected, count));
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
// Continue // Continue
} }
@@ -262,9 +266,10 @@ public class ContactListFragment extends BaseFragment implements EventListener,
if (e instanceof ContactAddedEvent) { if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading"); LOG.info("Contact added, reloading");
loadContacts(); loadContacts();
} else if (e instanceof ConnectionStatusChangedEvent) { } else if (e instanceof ContactConnectedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e; setConnected(((ContactConnectedEvent) e).getContactId(), true);
setConnectionStatus(c.getContactId(), c.getConnectionStatus()); } else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ContactRemovedEvent) { } else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item"); LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId()); removeItem(((ContactRemovedEvent) e).getContactId());
@@ -300,37 +305,14 @@ public class ContactListFragment extends BaseFragment implements EventListener,
} }
@UiThread @UiThread
private void setConnectionStatus(ContactId c, ConnectionStatus status) { private void setConnected(ContactId c, boolean connected) {
adapter.incrementRevision(); adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
item.setConnectionStatus(status); item.setConnected(connected);
adapter.updateItemAt(position, item); adapter.updateItemAt(position, item);
} }
} }
@UiThread
private void showSnackBar() {
if (snackbar != null) return;
View v = requireNonNull(getView());
int stringRes = R.string.pending_contact_requests_snackbar;
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, view -> showPendingContactList())
.make(v, stringRes, LENGTH_INDEFINITE);
snackbar.show();
}
@UiThread
private void dismissSnackBar() {
if (snackbar == null) return;
snackbar.dismiss();
snackbar = null;
}
private void showPendingContactList() {
Intent i = new Intent(getContext(), PendingContactListActivity.class);
startActivity(i);
}
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
@@ -16,9 +15,9 @@ public class ContactListItem extends ContactItem {
private long timestamp; private long timestamp;
private int unread; private int unread;
public ContactListItem(Contact contact, ConnectionStatus status, public ContactListItem(Contact contact, boolean connected,
GroupCount count) { GroupCount count) {
super(contact, status); super(contact, connected);
this.empty = count.getMsgCount() == 0; this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount(); this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime(); this.timestamp = count.getLatestMsgTime();

View File

@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Locale; import java.util.Locale;
@@ -14,11 +15,8 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.core.view.ViewCompat.setTransitionName; import static androidx.core.view.ViewCompat.setTransitionName;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -41,11 +39,10 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
// unread count // unread count
int unreadCount = item.getUnreadCount(); int unreadCount = item.getUnreadCount();
if (unreadCount > 0) { if (unreadCount > 0) {
unread.setText(String.format(Locale.getDefault(), "%d", unread.setText(String.format(Locale.getDefault(), "%d", unreadCount));
unreadCount)); unread.setVisibility(View.VISIBLE);
unread.setVisibility(VISIBLE);
} else { } else {
unread.setVisibility(INVISIBLE); unread.setVisibility(View.INVISIBLE);
} }
// date of last message // date of last message
@@ -57,7 +54,8 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
} }
ContactId c = item.getContact().getId(); ContactId c = item.getContact().getId();
setTransitionName(avatar, getAvatarTransitionName(c)); setTransitionName(avatar, UiUtils.getAvatarTransitionName(c));
setTransitionName(bulb, UiUtils.getBulbTransitionName(c));
} }
} }

View File

@@ -92,7 +92,7 @@ public class PendingContactListViewModel extends AndroidViewModel
Collection<Pair<PendingContact, PendingContactState>> pairs = Collection<Pair<PendingContact, PendingContactState>> pairs =
contactManager.getPendingContacts(); contactManager.getPendingContacts();
List<PendingContactItem> items = new ArrayList<>(pairs.size()); List<PendingContactItem> items = new ArrayList<>(pairs.size());
boolean online = pairs.isEmpty(); boolean online = items.isEmpty();
for (Pair<PendingContact, PendingContactState> pair : pairs) { for (Pair<PendingContact, PendingContactState> pair : pairs) {
PendingContact p = pair.getFirst(); PendingContact p = pair.getFirst();
PendingContactState state = pair.getSecond(); PendingContactState state = pair.getSecond();

View File

@@ -54,6 +54,8 @@ class PendingContactViewHolder extends ViewHolder {
status.setText(R.string.waiting_for_contact_to_come_online); status.setText(R.string.waiting_for_contact_to_come_online);
break; break;
case OFFLINE: case OFFLINE:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText(""); status.setText("");
break; break;
case CONNECTING: case CONNECTING:

View File

@@ -16,12 +16,6 @@ public interface SharingController {
@UiThread @UiThread
void setSharingListener(SharingListener listener); void setSharingListener(SharingListener listener);
/**
* Unsets the listener.
*/
@UiThread
void unsetSharingListener(SharingListener listener);
/** /**
* Call this when your lifecycle starts, * Call this when your lifecycle starts,
* so the listener will be called when information changes. * so the listener will be called when information changes.

View File

@@ -6,7 +6,8 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@@ -17,8 +18,6 @@ import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
@NotNullByDefault @NotNullByDefault
public class SharingControllerImpl implements SharingController, EventListener { public class SharingControllerImpl implements SharingController, EventListener {
@@ -44,11 +43,6 @@ public class SharingControllerImpl implements SharingController, EventListener {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetSharingListener(SharingListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void onStart() { public void onStart() {
eventBus.addListener(this); eventBus.addListener(this);
@@ -61,14 +55,15 @@ public class SharingControllerImpl implements SharingController, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ConnectionStatusChangedEvent) { if (e instanceof ContactConnectedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e; setConnected(((ContactConnectedEvent) e).getContactId());
setConnectionStatus(c.getContactId()); } else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId());
} }
} }
@UiThread @UiThread
private void setConnectionStatus(ContactId c) { private void setConnected(ContactId c) {
if (listener == null) throw new IllegalStateException(); if (listener == null) throw new IllegalStateException();
if (contacts.contains(c)) { if (contacts.contains(c)) {
int online = getOnlineCount(); int online = getOnlineCount();
@@ -95,9 +90,7 @@ public class SharingControllerImpl implements SharingController, EventListener {
public int getOnlineCount() { public int getOnlineCount() {
int online = 0; int online = 0;
for (ContactId c : contacts) { for (ContactId c : contacts) {
if (connectionRegistry.getConnectionStatus(c) == CONNECTED) { if (connectionRegistry.isConnected(c)) online++;
online++;
}
} }
return online; return online;
} }

View File

@@ -13,6 +13,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -33,8 +34,8 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent; import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
@@ -124,8 +125,6 @@ import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -139,6 +138,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
import static org.briarproject.briar.android.conversation.ImageActivity.DATE; import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME; import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName; import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
@@ -198,8 +198,8 @@ public class ConversationActivity extends BriarActivity
private ConversationAdapter adapter; private ConversationAdapter adapter;
private Toolbar toolbar; private Toolbar toolbar;
private CircleImageView toolbarAvatar; private CircleImageView toolbarAvatar;
private ImageView toolbarStatus;
private TextView toolbarTitle; private TextView toolbarTitle;
private TextView toolbarStatus;
private BriarRecyclerView list; private BriarRecyclerView list;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
private TextInputView textInputView; private TextInputView textInputView;
@@ -237,8 +237,8 @@ public class ConversationActivity extends BriarActivity
// Custom Toolbar // Custom Toolbar
toolbar = requireNonNull(setUpCustomToolbar(true)); toolbar = requireNonNull(setUpCustomToolbar(true));
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar); toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarTitle = toolbar.findViewById(R.id.contactName);
toolbarStatus = toolbar.findViewById(R.id.contactStatus); toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
observeOnce(viewModel.getContactAuthorId(), this, authorId -> { observeOnce(viewModel.getContactAuthorId(), this, authorId -> {
requireNonNull(authorId); requireNonNull(authorId);
@@ -257,6 +257,7 @@ public class ConversationActivity extends BriarActivity
this::onAddedPrivateMessage); this::onAddedPrivateMessage);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId)); setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, this, visitor = new ConversationVisitor(this, this, this,
viewModel.getContactDisplayName()); viewModel.getContactDisplayName());
@@ -498,14 +499,14 @@ public class ConversationActivity extends BriarActivity
@UiThread @UiThread
private void displayContactOnlineStatus() { private void displayContactOnlineStatus() {
ConnectionStatus status = if (connectionRegistry.isConnected(contactId)) {
connectionRegistry.getConnectionStatus(contactId); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
if (status == CONNECTED) { ConversationActivity.this, R.drawable.contact_online));
toolbarStatus.setText(R.string.online); toolbarStatus.setContentDescription(getString(R.string.online));
} else if (status == RECENTLY_CONNECTED) {
toolbarStatus.setText(R.string.recently_online);
} else { } else {
toolbarStatus.setText(R.string.offline); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_offline));
toolbarStatus.setContentDescription(getString(R.string.offline));
} }
} }
@@ -728,10 +729,16 @@ public class ConversationActivity extends BriarActivity
LOG.info("Messages acked"); LOG.info("Messages acked");
markMessages(m.getMessageIds(), true, true); markMessages(m.getMessageIds(), true, true);
} }
} else if (e instanceof ConnectionStatusChangedEvent) { } else if (e instanceof ContactConnectedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e; ContactConnectedEvent c = (ContactConnectedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
LOG.info("Connection status changed"); LOG.info("Contact connected");
displayContactOnlineStatus();
}
} else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected");
displayContactOnlineStatus(); displayContactOnlineStatus();
} }
} else if (e instanceof ClientVersionUpdatedEvent) { } else if (e instanceof ClientVersionUpdatedEvent) {

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -74,8 +73,7 @@ public class ContactChooserFragment extends BaseFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false); View contentView = inflater.inflate(R.layout.list, container, false);
@@ -129,9 +127,9 @@ public class ContactChooserFragment extends BaseFragment {
ContactId id = c.getId(); ContactId id = c.getId();
GroupCount count = GroupCount count =
conversationManager.getGroupCount(id); conversationManager.getGroupCount(id);
ConnectionStatus status = connectionRegistry boolean connected =
.getConnectionStatus(c.getId()); connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, status, count)); contacts.add(new ContactListItem(c, connected, count));
} }
} }
displayContacts(contacts); displayContacts(contacts);

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@@ -79,13 +80,13 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
@UiThread @UiThread
private void contactExchangeFailed() { private void contactExchangeFailed() {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@Override @Override
public void keyAgreementFailed() { public void keyAgreementFailed() {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@@ -103,7 +104,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
@UiThread @UiThread
@Override @Override
public void keyAgreementAborted(boolean remoteAborted) { public void keyAgreementAborted(boolean remoteAborted) {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment();
} }
@UiThread @UiThread
@@ -112,4 +113,10 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
startContactExchange(result); startContactExchange(result);
return getString(R.string.exchanging_contact_details); return getString(R.string.exchanging_contact_details);
} }
protected void showErrorFragment() {
String errorMsg = getString(R.string.connection_error_explanation);
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
showNextFragment(f);
}
} }

View File

@@ -8,10 +8,18 @@ import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
@@ -37,13 +45,14 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.CAMERA;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
@@ -51,10 +60,33 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMI
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class KeyAgreementActivity extends BriarActivity implements public abstract class KeyAgreementActivity extends BriarActivity implements
BaseFragmentListener, IntroScreenSeenListener, BaseFragmentListener, IntroScreenSeenListener,
KeyAgreementEventListener { KeyAgreementEventListener, EventListener {
private enum BluetoothState { private enum BluetoothDecision {
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE /**
* We haven't asked the user about Bluetooth discoverability.
*/
UNKNOWN,
/**
* The device doesn't have a Bluetooth adapter.
*/
NO_ADAPTER,
/**
* We're waiting for the user to accept or refuse discoverability.
*/
WAITING,
/**
* The user has accepted discoverability.
*/
ACCEPTED,
/**
* The user has refused discoverability.
*/
REFUSED
} }
private enum Permission { private enum Permission {
@@ -62,11 +94,14 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} }
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(KeyAgreementActivity.class.getName()); getLogger(KeyAgreementActivity.class.getName());
@Inject @Inject
EventBus eventBus; EventBus eventBus;
@Inject
PluginManager pluginManager;
/** /**
* Set to true in onPostResume() and false in onPause(). This prevents the * Set to true in onPostResume() and false in onPause(). This prevents the
* QR code fragment from being shown if onRequestPermissionsResult() is * QR code fragment from being shown if onRequestPermissionsResult() is
@@ -74,21 +109,36 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
* https://issuetracker.google.com/issues/37067655. * https://issuetracker.google.com/issues/37067655.
*/ */
private boolean isResumed = false; private boolean isResumed = false;
/** /**
* Set to true when the continue button is clicked, and false when the QR * Set to true when the continue button is clicked, and false when the QR
* code fragment is shown. This prevents the QR code fragment from being * code fragment is shown. This prevents the QR code fragment from being
* shown automatically before the continue button has been clicked. * shown automatically before the continue button has been clicked.
*/ */
private boolean continueClicked = false; private boolean continueClicked = false;
/** /**
* Records whether the Bluetooth adapter was already enabled before we * Records whether the Bluetooth adapter was already enabled before we
* asked for Bluetooth discoverability, so we know whether to broadcast a * asked for Bluetooth discoverability, so we know whether to broadcast a
* {@link BluetoothEnabledEvent}. * {@link BluetoothEnabledEvent}.
*/ */
private boolean wasAdapterEnabled = false; private boolean wasAdapterEnabled = false;
/**
* Records whether we've enabled the wifi plugin so we don't enable it more
* than once.
*/
private boolean hasEnabledWifi = false;
/**
* Records whether we've enabled the Bluetooth plugin so we don't enable it
* more than once.
*/
private boolean hasEnabledBluetooth = false;
private Permission cameraPermission = Permission.UNKNOWN; private Permission cameraPermission = Permission.UNKNOWN;
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
private BluetoothState bluetoothState = BluetoothState.UNKNOWN; private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
private BroadcastReceiver bluetoothReceiver = null; private BroadcastReceiver bluetoothReceiver = null;
@Override @Override
@@ -96,20 +146,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
component.inject(this); component.inject(this);
} }
@SuppressWarnings("ConstantConditions")
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_fragment_container_toolbar); setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
if (state == null) { if (state == null) {
showInitialFragment(IntroFragment.newInstance()); showInitialFragment(IntroFragment.newInstance());
} }
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
bluetoothReceiver = new BluetoothStateReceiver(); bluetoothReceiver = new BluetoothStateReceiver();
registerReceiver(bluetoothReceiver, filter); registerReceiver(bluetoothReceiver, filter);
} }
@@ -122,18 +169,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { if (item.getItemId() == android.R.id.home) {
case android.R.id.home: onBackPressed();
onBackPressed(); return true;
return true;
default:
return super.onOptionsItemSelected(item);
} }
return super.onOptionsItemSelected(item);
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this);
// Permissions may have been granted manually while we were stopped // Permissions may have been granted manually while we were stopped
cameraPermission = Permission.UNKNOWN; cameraPermission = Permission.UNKNOWN;
locationPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN;
@@ -150,11 +196,22 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private void showQrCodeFragmentIfAllowed() { private void showQrCodeFragmentIfAllowed() {
if (isResumed && continueClicked && areEssentialPermissionsGranted()) { if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
if (bluetoothState == BluetoothState.UNKNOWN || if (isWifiReady() && isBluetoothReady()) {
bluetoothState == BluetoothState.ENABLED) { LOG.info("Wifi and Bluetooth are ready");
requestBluetoothDiscoverable();
} else if (bluetoothState != BluetoothState.WAITING) {
showQrCodeFragment(); showQrCodeFragment();
} else {
if (shouldEnableWifi()) {
LOG.info("Enabling wifi plugin");
hasEnabledWifi = true;
pluginManager.setPluginEnabled(LanTcpConstants.ID, true);
}
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
requestBluetoothDiscoverable();
} else if (shouldEnableBluetooth()) {
LOG.info("Enabling Bluetooth plugin");
hasEnabledBluetooth = true;
pluginManager.setPluginEnabled(BluetoothConstants.ID, true);
}
} }
} }
} }
@@ -167,57 +224,104 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
locationPermission == Permission.PERMANENTLY_DENIED); locationPermission == Permission.PERMANENTLY_DENIED);
} }
private boolean isWifiReady() {
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return true; // Continue without wifi
State state = p.getState();
// Wait for plugin to become enabled
return state == ACTIVE || state == INACTIVE;
}
private boolean isBluetoothReady() {
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
bluetoothDecision == BluetoothDecision.WAITING) {
// Wait for decision
return false;
}
if (bluetoothDecision == BluetoothDecision.NO_ADAPTER
|| bluetoothDecision == BluetoothDecision.REFUSED) {
// Continue without Bluetooth
return true;
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) return true; // Continue without Bluetooth
if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
// Wait for adapter to become discoverable
return false;
}
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
if (p == null) return true; // Continue without Bluetooth
// Wait for plugin to become active
return p.getState() == ACTIVE;
}
private boolean shouldEnableWifi() {
if (hasEnabledWifi) return false;
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
return p != null && p.getState() == DISABLED;
}
private void requestBluetoothDiscoverable() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
showQrCodeFragmentIfAllowed();
} else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability");
bluetoothDecision = BluetoothDecision.WAITING;
wasAdapterEnabled = bt.isEnabled();
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
} else {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
showQrCodeFragmentIfAllowed();
}
}
}
private boolean shouldEnableBluetooth() {
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
if (hasEnabledBluetooth) return false;
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
return p != null && p.getState() == DISABLED;
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
isResumed = false; isResumed = false;
} }
@Override
protected void onStop() {
super.onStop();
eventBus.removeListener(this);
}
@Override @Override
public void showNextScreen() { public void showNextScreen() {
continueClicked = true; continueClicked = true;
if (checkPermissions()) showQrCodeFragmentIfAllowed(); if (checkPermissions()) showQrCodeFragmentIfAllowed();
} }
private void requestBluetoothDiscoverable() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
setBluetoothState(BluetoothState.NO_ADAPTER);
} else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
if (i.resolveActivity(getPackageManager()) != null) {
setBluetoothState(BluetoothState.WAITING);
wasAdapterEnabled = bt.isEnabled();
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
} else {
setBluetoothState(BluetoothState.NO_ADAPTER);
}
}
}
private void setBluetoothState(BluetoothState bluetoothState) {
LOG.info("Setting Bluetooth state to " + bluetoothState);
this.bluetoothState = bluetoothState;
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
eventBus.broadcast(new BluetoothEnabledEvent());
wasAdapterEnabled = true;
}
showQrCodeFragmentIfAllowed();
}
@Override @Override
public void onActivityResult(int request, int result, Intent data) { public void onActivityResult(int request, int result,
@Nullable Intent data) {
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) { if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
if (result == RESULT_CANCELED) { if (result == RESULT_CANCELED) {
setBluetoothState(BluetoothState.REFUSED); LOG.info("Bluetooth discoverability was refused");
bluetoothDecision = BluetoothDecision.REFUSED;
} else { } else {
// If Bluetooth is already discoverable, show the QR code - LOG.info("Bluetooth discoverability was accepted");
// otherwise wait for the state or scan mode to change bluetoothDecision = BluetoothDecision.ACCEPTED;
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); if (!wasAdapterEnabled) {
if (bt == null) throw new AssertionError(); LOG.info("Bluetooth adapter was enabled by us");
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) eventBus.broadcast(new BluetoothEnabledEvent());
setBluetoothState(BluetoothState.DISCOVERABLE); wasAdapterEnabled = true;
}
} }
showQrCodeFragmentIfAllowed();
} else super.onActivityResult(request, result, data); } else super.onActivityResult(request, result, data);
} }
@@ -227,7 +331,12 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
continueClicked = false; continueClicked = false;
// If we return to the intro fragment, ask for Bluetooth // If we return to the intro fragment, ask for Bluetooth
// discoverability again before showing the QR code fragment // discoverability again before showing the QR code fragment
bluetoothState = BluetoothState.UNKNOWN; bluetoothDecision = BluetoothDecision.UNKNOWN;
// If we return to the intro fragment, we may need to enable wifi and
// Bluetooth again
hasEnabledWifi = false;
hasEnabledBluetooth = false;
// FIXME #824 // FIXME #824
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
@@ -239,12 +348,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} }
} }
protected void showErrorFragment(@StringRes int errorResId) {
String errorMsg = getString(errorResId);
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
showNextFragment(f);
}
private boolean checkPermissions() { private boolean checkPermissions() {
if (areEssentialPermissionsGranted()) return true; if (areEssentialPermissionsGranted()) return true;
// If the camera permission has been permanently denied, ask the // If the camera permission has been permanently denied, ask the
@@ -335,24 +438,30 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
permission); permission);
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportStateEvent) {
TransportStateEvent t = (TransportStateEvent) e;
if (t.getTransportId().equals(BluetoothConstants.ID)) {
if (LOG.isLoggable(INFO)) {
LOG.info("Bluetooth state changed to " + t.getState());
}
showQrCodeFragmentIfAllowed();
} else if (t.getTransportId().equals(LanTcpConstants.ID)) {
if (LOG.isLoggable(INFO)) {
LOG.info("Wifi state changed to " + t.getState());
}
showQrCodeFragmentIfAllowed();
}
}
}
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); LOG.info("Bluetooth scan mode changed");
if (ACTION_STATE_CHANGED.equals(action)) { showQrCodeFragmentIfAllowed();
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON)
setBluetoothState(BluetoothState.ENABLED);
else setBluetoothState(BluetoothState.UNKNOWN);
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
setBluetoothState(BluetoothState.DISCOVERABLE);
else if (scanMode == SCAN_MODE_CONNECTABLE)
setBluetoothState(BluetoothState.ENABLED);
else setBluetoothState(BluetoothState.UNKNOWN);
}
} }
} }
} }

View File

@@ -15,33 +15,27 @@ import android.widget.Toast;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.VisibleForTesting; import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.util.UiUtils.setError;
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard; import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
public class ChangePasswordActivity extends BriarActivity public class ChangePasswordActivity extends BriarActivity
implements OnClickListener, OnEditorActionListener { implements OnClickListener, OnEditorActionListener {
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; protected ChangePasswordController passwordController;
private TextInputLayout currentPasswordEntryWrapper; private TextInputLayout currentPasswordEntryWrapper;
private TextInputLayout newPasswordEntryWrapper; private TextInputLayout newPasswordEntryWrapper;
@@ -53,17 +47,11 @@ public class ChangePasswordActivity extends BriarActivity
private Button changePasswordButton; private Button changePasswordButton;
private ProgressBar progress; private ProgressBar progress;
@VisibleForTesting
ChangePasswordViewModel viewModel;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_change_password); setContentView(R.layout.activity_change_password);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ChangePasswordViewModel.class);
currentPasswordEntryWrapper = currentPasswordEntryWrapper =
findViewById(R.id.current_password_entry_wrapper); findViewById(R.id.current_password_entry_wrapper);
newPasswordEntryWrapper = findViewById(R.id.new_password_entry_wrapper); newPasswordEntryWrapper = findViewById(R.id.new_password_entry_wrapper);
@@ -114,12 +102,13 @@ public class ChangePasswordActivity extends BriarActivity
String firstPassword = newPassword.getText().toString(); String firstPassword = newPassword.getText().toString();
String secondPassword = newPasswordConfirmation.getText().toString(); String secondPassword = newPasswordConfirmation.getText().toString();
boolean passwordsMatch = firstPassword.equals(secondPassword); boolean passwordsMatch = firstPassword.equals(secondPassword);
float strength = viewModel.estimatePasswordStrength(firstPassword); float strength =
passwordController.estimatePasswordStrength(firstPassword);
strengthMeter.setStrength(strength); strengthMeter.setStrength(strength);
setError(newPasswordEntryWrapper, UiUtils.setError(newPasswordEntryWrapper,
getString(R.string.password_too_weak), getString(R.string.password_too_weak),
firstPassword.length() > 0 && strength < QUITE_WEAK); firstPassword.length() > 0 && strength < QUITE_WEAK);
setError(newPasswordConfirmationWrapper, UiUtils.setError(newPasswordConfirmationWrapper,
getString(R.string.passwords_do_not_match), getString(R.string.passwords_do_not_match),
secondPassword.length() > 0 && !passwordsMatch); secondPassword.length() > 0 && !passwordsMatch);
changePasswordButton.setEnabled( changePasswordButton.setEnabled(
@@ -138,34 +127,32 @@ public class ChangePasswordActivity extends BriarActivity
// Replace the button with a progress bar // Replace the button with a progress bar
changePasswordButton.setVisibility(INVISIBLE); changePasswordButton.setVisibility(INVISIBLE);
progress.setVisibility(VISIBLE); progress.setVisibility(VISIBLE);
passwordController.changePassword(currentPassword.getText().toString(),
String curPwd = currentPassword.getText().toString(); newPassword.getText().toString(),
String newPwd = newPassword.getText().toString(); new UiResultHandler<Boolean>(this) {
viewModel.changePassword(curPwd, newPwd).observeEvent(this, result -> { @Override
if (result == SUCCESS) { public void onResultUi(@NonNull Boolean result) {
Toast.makeText(ChangePasswordActivity.this, if (result) {
R.string.password_changed, Toast.makeText(ChangePasswordActivity.this,
LENGTH_LONG).show(); R.string.password_changed,
setResult(RESULT_OK); Toast.LENGTH_LONG).show();
supportFinishAfterTransition(); setResult(RESULT_OK);
} else { supportFinishAfterTransition();
tryAgain(result); } else {
tryAgain();
}
} }
} });
);
} }
private void tryAgain(DecryptionResult result) { private void tryAgain() {
UiUtils.setError(currentPasswordEntryWrapper,
getString(R.string.try_again), true);
changePasswordButton.setVisibility(VISIBLE); changePasswordButton.setVisibility(VISIBLE);
progress.setVisibility(INVISIBLE); progress.setVisibility(INVISIBLE);
if (result == KEY_STRENGTHENER_ERROR) { currentPassword.setText("");
createKeyStrengthenerErrorDialog(this).show();
} else { // show the keyboard again
setError(currentPasswordEntryWrapper, showSoftKeyboard(currentPassword);
getString(R.string.try_again), true);
currentPassword.setText("");
// show the keyboard again
showSoftKeyboard(currentPassword);
}
} }
} }

View File

@@ -0,0 +1,43 @@
package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@NotNullByDefault
public class ChangePasswordControllerImpl implements ChangePasswordController {
protected final AccountManager accountManager;
protected final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
ChangePasswordControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void changePassword(String oldPassword, String newPassword,
ResultHandler<Boolean> resultHandler) {
ioExecutor.execute(() -> {
boolean changed =
accountManager.changePassword(oldPassword, newPassword);
resultHandler.onResult(changed);
});
}
}

View File

@@ -1,53 +0,0 @@
package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import androidx.lifecycle.ViewModel;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
@NotNullByDefault
public class ChangePasswordViewModel extends ViewModel {
private final AccountManager accountManager;
private final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
ChangePasswordViewModel(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
LiveEvent<DecryptionResult> changePassword(String oldPassword,
String newPassword) {
MutableLiveEvent<DecryptionResult> result = new MutableLiveEvent<>();
ioExecutor.execute(() -> {
try {
accountManager.changePassword(oldPassword, newPassword);
result.postEvent(SUCCESS);
} catch (DecryptionException e) {
result.postEvent(e.getDecryptionResult());
}
});
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More