diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java
index d0d9c5ae0..b7247967c 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/Plugin.java
@@ -34,6 +34,13 @@ public interface Plugin {
INACTIVE
}
+ /**
+ * 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;
+
/**
* Returns the plugin's transport identifier.
*/
@@ -64,6 +71,16 @@ public interface Plugin {
*/
State getState();
+ /**
+ * Returns an integer code indicating why the plugin is
+ * {@link State#DISABLED disabled}, or -1 if the plugin is not disabled.
+ *
+ * The codes used are plugin-specific, except the generic code
+ * {@link #REASON_STARTING_STOPPING}, which may be used by
+ * any plugin.
+ */
+ int getReasonDisabled();
+
/**
* Returns true if the plugin should be polled periodically to attempt to
* establish connections.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java
index 6be7582a1..2d808eaa9 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TorConstants.java
@@ -23,4 +23,8 @@ public interface TorConstants {
int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
int PREF_TOR_NETWORK_NEVER = 3;
+ int REASON_USER = 1;
+ int REASON_BATTERY = 2;
+ int REASON_MOBILE_DATA = 3;
+ int REASON_COUNTRY_BLOCKED = 4;
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
index c34bb771f..f4e8bb128 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java
@@ -262,6 +262,11 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener {
return state.getState();
}
+ @Override
+ public int getReasonDisabled() {
+ return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
+ }
+
@Override
public boolean shouldPoll() {
return true;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
index 627e1d680..e92314ee5 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/TcpPlugin.java
@@ -202,6 +202,11 @@ abstract class TcpPlugin implements DuplexPlugin {
return state.getState();
}
+ @Override
+ public int getReasonDisabled() {
+ return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
+ }
+
@Override
public boolean shouldPoll() {
return true;
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
index 1b09fcfb5..a32eb7b2c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
@@ -57,6 +57,7 @@ import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory;
@@ -84,6 +85,10 @@ 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.PROP_ONION_V2;
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.api.plugin.TorConstants.REASON_USER;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -521,6 +526,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return state.getState();
}
+ @Override
+ public int getReasonDisabled() {
+ return state.getReasonDisabled();
+ }
+
@Override
public boolean shouldPoll() {
return true;
@@ -754,62 +764,91 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!state.isRunning()) return;
- boolean online = status.isConnected();
- boolean wifi = status.isWifi();
- String country = locationUtils.getCurrentCountry();
- boolean blocked =
- circumventionProvider.isTorProbablyBlocked(country);
- int network = settings.getInt(PREF_TOR_NETWORK,
- PREF_TOR_NETWORK_AUTOMATIC);
- boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
- boolean onlyWhenCharging =
- settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
- boolean bridgesWork = circumventionProvider.doBridgesWork(country);
- boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
+ NetworkConfig config = getNetworkConfig(status, charging);
+ state.setDisabledBySettings(config.disabledBySettings,
+ config.reasonDisabled);
+ callback.pluginStateChanged(getState());
+ applyNetworkConfig(config);
+ });
+ }
- if (LOG.isLoggable(INFO)) {
- LOG.info("Online: " + online + ", wifi: " + wifi);
- if (country.isEmpty()) LOG.info("Country code unknown");
- else LOG.info("Country code: " + country);
- LOG.info("Charging: " + charging);
+ private NetworkConfig getNetworkConfig(NetworkStatus status,
+ boolean charging) {
+ boolean online = status.isConnected();
+ boolean wifi = status.isWifi();
+ String country = locationUtils.getCurrentCountry();
+ boolean blocked = circumventionProvider.isTorProbablyBlocked(country);
+ int network =
+ settings.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
+ boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
+ boolean onlyWhenCharging =
+ settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
+ boolean bridgesWork = circumventionProvider.doBridgesWork(country);
+ boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
+
+ if (LOG.isLoggable(INFO)) {
+ LOG.info("Online: " + online + ", wifi: " + wifi);
+ if (country.isEmpty()) LOG.info("Country code unknown");
+ else LOG.info("Country code: " + country);
+ LOG.info("Charging: " + charging);
+ }
+
+ boolean enableNetwork = false, enableBridges = false;
+ boolean useMeek = false, enableConnectionPadding = false;
+ boolean disabledBySettings = false;
+ int reasonDisabled = REASON_STARTING_STOPPING;
+
+ if (!online) {
+ LOG.info("Disabling network, device is offline");
+ } else if (network == PREF_TOR_NETWORK_NEVER) {
+ LOG.info("Disabling network, user has disabled Tor");
+ disabledBySettings = true;
+ reasonDisabled = REASON_USER;
+ } else if (!charging && onlyWhenCharging) {
+ LOG.info("Disabling network, device is on battery");
+ disabledBySettings = true;
+ 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 if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
+ (automatic && bridgesWork)) {
+ if (circumventionProvider.needsMeek(country)) {
+ LOG.info("Enabling network, using meek bridges");
+ enableBridges = true;
+ useMeek = true;
+ } else {
+ LOG.info("Enabling network, using obfs4 bridges");
+ enableBridges = true;
}
+ enableNetwork = true;
+ } else {
+ LOG.info("Enabling network, not using bridges");
+ enableNetwork = true;
+ }
+ if (online && wifi && charging) {
+ LOG.info("Enabling connection padding");
+ enableConnectionPadding = true;
+ } else {
+ LOG.info("Disabling connection padding");
+ }
+
+ return new NetworkConfig(enableNetwork, enableBridges, useMeek,
+ enableConnectionPadding, disabledBySettings, reasonDisabled);
+ }
+
+ private void applyNetworkConfig(NetworkConfig config) {
+ connectionStatusExecutor.execute(() -> {
try {
- if (!online) {
- LOG.info("Disabling network, device is offline");
- enableNetwork(false);
- } else if (!charging && onlyWhenCharging) {
- LOG.info("Disabling network, device is on battery");
- enableNetwork(false);
- } else if (network == PREF_TOR_NETWORK_NEVER ||
- (!useMobile && !wifi)) {
- LOG.info("Disabling network, device is using mobile data");
- enableNetwork(false);
- } else if (automatic && blocked && !bridgesWork) {
- LOG.info("Disabling network, country is blocked");
- enableNetwork(false);
- } else if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
- (automatic && bridgesWork)) {
- if (circumventionProvider.needsMeek(country)) {
- LOG.info("Enabling network, using meek bridges");
- enableBridges(true, true);
- } else {
- LOG.info("Enabling network, using obfs4 bridges");
- enableBridges(true, false);
- }
- enableNetwork(true);
- } else {
- LOG.info("Enabling network, not using bridges");
- enableBridges(false, false);
- enableNetwork(true);
- }
- if (online && wifi && charging) {
- LOG.info("Enabling connection padding");
- enableConnectionPadding(true);
- } else {
- LOG.info("Disabling connection padding");
- enableConnectionPadding(false);
- }
+ enableBridges(config.enableBridges, config.useMeek);
+ enableNetwork(config.enableNetwork);
+ enableConnectionPadding(config.enableConnectionPadding);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
@@ -821,6 +860,26 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
+ @Immutable
+ @NotNullByDefault
+ private static class NetworkConfig {
+
+ private final boolean enableNetwork, enableBridges, useMeek;
+ private final boolean enableConnectionPadding, disabledBySettings;
+ private final int reasonDisabled;
+
+ private NetworkConfig(boolean enableNetwork, boolean enableBridges,
+ boolean useMeek, boolean enableConnectionPadding,
+ boolean disabledBySettings, int reasonDisabled) {
+ this.enableNetwork = enableNetwork;
+ this.enableBridges = enableBridges;
+ this.useMeek = useMeek;
+ this.enableConnectionPadding = enableConnectionPadding;
+ this.disabledBySettings = disabledBySettings;
+ this.reasonDisabled = reasonDisabled;
+ }
+ }
+
@ThreadSafe
@NotNullByDefault
protected static class PluginState {
@@ -831,7 +890,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
networkInitialised = false,
networkEnabled = false,
bootstrapped = false,
- circuitBuilt = false;
+ circuitBuilt = false,
+ disabledBySettings = false;
+
+ @GuardedBy("this")
+ private int reasonDisabled = REASON_STARTING_STOPPING;
@GuardedBy("this")
@Nullable
@@ -869,6 +932,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!enable) circuitBuilt = false;
}
+ synchronized void setDisabledBySettings(boolean disabledBySettings,
+ int reasonDisabled) {
+ this.disabledBySettings = disabledBySettings;
+ this.reasonDisabled = reasonDisabled;
+ }
+
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
@@ -880,10 +949,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
synchronized State getState() {
- if (!started || stopped) return DISABLED;
+ 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;
+ }
}
}
diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
index aebf61fd6..89a11ff2f 100644
--- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
+++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemPlugin.java
@@ -124,6 +124,11 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return state.getState();
}
+ @Override
+ public int getReasonDisabled() {
+ return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
+ }
+
@Override
public boolean shouldPoll() {
return false;