diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index 839dee2c6..2c6e37051 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -146,6 +146,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { wasEnabledByUs = true; } + @Override + void onAdapterDisabled() { + super.onAdapterDisabled(); + wasEnabledByUs = false; + } + @Override @Nullable String getBluetoothAddress() { diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java index ef6cb8efa..577a773e7 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tcp/AndroidLanTcpPlugin.java @@ -32,7 +32,11 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.os.Build.VERSION.SDK_INT; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.logging.Level.WARNING; 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 class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { @@ -79,16 +83,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { @Override public void start() { if (used.getAndSet(true)) throw new IllegalStateException(); - running = true; + state.setStarted(); updateConnectionStatus(); } - @Override - public void stop() { - running = false; - tryToClose(socket); - } - @Override protected Socket createSocket() throws IOException { return socketFactory.createSocket(); @@ -143,7 +141,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { private void updateConnectionStatus() { connectionStatusExecutor.execute(() -> { - if (!running) return; + State s = getState(); + if (s != ACTIVE && s != INACTIVE) return; Collection addrs = getLocalIpAddresses(); if (addrs.contains(WIFI_AP_ADDRESS)) { LOG.info("Providing wifi hotspot"); @@ -152,15 +151,21 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { // make outgoing connections on API 21+ if another network // has internet access socketFactory = SocketFactory.getDefault(); - if (socket == null || socket.isClosed()) bind(); + if (s == INACTIVE) bind(); } else if (addrs.isEmpty()) { LOG.info("Not connected to wifi"); 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 { LOG.info("Connected to wifi"); socketFactory = getSocketFactory(); - if (socket == null || socket.isClosed()) bind(); + if (s == INACTIVE) bind(); } }); } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java index f36773615..528e611b0 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java @@ -74,7 +74,6 @@ class AndroidTorPlugin extends TorPlugin { @Override protected void enableNetwork(boolean enable) throws IOException { - if (!running) return; if (enable) wakeLock.acquire(); super.enableNetwork(enable); if (!enable) wakeLock.release(); diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidLocationUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidLocationUtils.java index 226064d6f..21d2075c1 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidLocationUtils.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidLocationUtils.java @@ -61,12 +61,12 @@ class AndroidLocationUtils implements LocationUtils { private String getCountryFromPhoneNetwork() { Object o = appContext.getSystemService(TELEPHONY_SERVICE); TelephonyManager tm = (TelephonyManager) o; - return tm.getNetworkCountryIso(); + return tm == null ? "" : tm.getNetworkCountryIso(); } private String getCountryFromSimCard() { Object o = appContext.getSystemService(TELEPHONY_SERVICE); TelephonyManager tm = (TelephonyManager) o; - return tm.getSimCountryIso(); + return tm == null ? "" : tm.getSimCountryIso(); } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/event/EventBus.java b/bramble-api/src/main/java/org/briarproject/bramble/api/event/EventBus.java index d27449833..cf57b67ed 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/event/EventBus.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/event/EventBus.java @@ -18,6 +18,8 @@ public interface EventBus { /** * Asynchronously notifies all listeners of an event. Listeners are * notified on the {@link EventExecutor}. + *

+ * This method can safely be called while holding a lock. */ void broadcast(Event e); } 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 84872dfed..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 @@ -9,6 +9,38 @@ import java.util.Collection; @NotNullByDefault 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 + } + + /** + * 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. */ @@ -35,9 +67,19 @@ public interface Plugin { 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. + *

+ * 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 diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java index b9cbd450b..1b149cde1 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/PluginCallback.java @@ -1,6 +1,10 @@ package org.briarproject.bramble.api.plugin; 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.settings.Settings; @@ -32,12 +36,17 @@ public interface PluginCallback extends ConnectionHandler { void mergeLocalProperties(TransportProperties p); /** - * Signals that the transport is enabled. + * Informs the callback of the plugin's current state. + *

+ * 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}. + *

+ * This method can safely be called while holding a lock. */ - void transportEnabled(); - - /** - * Signals that the transport is disabled. - */ - void transportDisabled(); + void pluginStateChanged(State state); } 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-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportDisabledEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportActiveEvent.java similarity index 64% rename from bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportDisabledEvent.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportActiveEvent.java index 1d415ca9f..de9c49c90 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportDisabledEvent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportActiveEvent.java @@ -2,20 +2,22 @@ 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 a transport is disabled. + * An event that is broadcast when a plugin enters the {@link State#ACTIVE} + * state. */ @Immutable @NotNullByDefault -public class TransportDisabledEvent extends Event { +public class TransportActiveEvent extends Event { private final TransportId transportId; - public TransportDisabledEvent(TransportId transportId) { + public TransportActiveEvent(TransportId transportId) { this.transportId = transportId; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportEnabledEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportInactiveEvent.java similarity index 64% rename from bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportEnabledEvent.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportInactiveEvent.java index 7064c9b9a..e2167c0f5 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportEnabledEvent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportInactiveEvent.java @@ -2,20 +2,22 @@ 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 a transport is enabled. + * An event that is broadcast when a plugin leaves the {@link State#ACTIVE} + * state. */ @Immutable @NotNullByDefault -public class TransportEnabledEvent extends Event { +public class TransportInactiveEvent extends Event { private final TransportId transportId; - public TransportEnabledEvent(TransportId transportId) { + public TransportInactiveEvent(TransportId transportId) { this.transportId = transportId; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportStateEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportStateEvent.java new file mode 100644 index 000000000..376b5303f --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/event/TransportStateEvent.java @@ -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; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java index d4d69ca56..c1e9ced33 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java @@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.Plugin.State; import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginConfig; 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.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +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.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; 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.Executor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import javax.annotation.concurrent.ThreadSafe; @@ -45,6 +48,8 @@ import static java.util.logging.Level.FINE; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; 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.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; @@ -250,7 +255,8 @@ class PluginManagerImpl implements PluginManager, Service { private class Callback implements PluginCallback { private final TransportId id; - private final AtomicBoolean enabled = new AtomicBoolean(false); + private final AtomicReference state = + new AtomicReference<>(DISABLED); private Callback(TransportId id) { this.id = id; @@ -295,15 +301,20 @@ class PluginManagerImpl implements PluginManager, Service { } @Override - public void transportEnabled() { - if (!enabled.getAndSet(true)) - eventBus.broadcast(new TransportEnabledEvent(id)); - } - - @Override - public void transportDisabled() { - if (enabled.getAndSet(false)) - eventBus.broadcast(new TransportDisabledEvent(id)); + public void pluginStateChanged(State newState) { + State oldState = state.getAndSet(newState); + if (newState != oldState) { + if (LOG.isLoggable(INFO)) { + LOG.info(id + " changed from state " + oldState + + " to " + newState); + } + eventBus.broadcast(new TransportStateEvent(id, newState)); + if (newState == ACTIVE) { + eventBus.broadcast(new TransportActiveEvent(id)); + } else if (oldState == ACTIVE) { + eventBus.broadcast(new TransportInactiveEvent(id)); + } + } } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java index 256bf215e..3a4f51c8d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java @@ -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.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; @@ -106,13 +106,13 @@ class PollerImpl implements Poller, EventListener { ConnectionOpenedEvent c = (ConnectionOpenedEvent) e; // Reschedule polling, the polling interval may have decreased reschedule(c.getTransportId()); - } else if (e instanceof TransportEnabledEvent) { - TransportEnabledEvent t = (TransportEnabledEvent) e; - // Poll the newly enabled transport + } else if (e instanceof TransportActiveEvent) { + TransportActiveEvent t = (TransportActiveEvent) e; + // Poll the newly activated transport pollNow(t.getTransportId()); - } else if (e instanceof TransportDisabledEvent) { - TransportDisabledEvent t = (TransportDisabledEvent) e; - // Cancel polling for the disabled transport + } else if (e instanceof TransportInactiveEvent) { + TransportInactiveEvent t = (TransportInactiveEvent) e; + // Cancel polling for the deactivated transport cancel(t.getTransportId()); } } 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 0dec35bc5..e15a3e623 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 @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.ConnectionHandler; @@ -36,6 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; 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.WARNING; @@ -46,6 +49,9 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENA import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; +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.PrivacyUtils.scrubMacAddress; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @@ -68,9 +74,10 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { private final int maxLatency; private final AtomicBoolean used = new AtomicBoolean(false); - private volatile boolean running = false, contactConnections = false; + protected final PluginState state = new PluginState(); + + private volatile boolean contactConnections = false; private volatile String contactConnectionsUuid = null; - private volatile SS socket = null; abstract void initialiseAdapter() throws IOException; @@ -120,13 +127,15 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { // We may not have been able to get the local address before ioExecutor.execute(this::updateProperties); if (shouldAllowContactConnections()) bind(); + callback.pluginStateChanged(getState()); } void onAdapterDisabled() { LOG.info("Bluetooth disabled"); - tryToClose(socket); connectionLimiter.allConnectionsClosed(); - callback.transportDisabled(); + // The server socket may not have been closed automatically + tryToClose(state.clearServerSocket()); + callback.pluginStateChanged(getState()); } @Override @@ -154,7 +163,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { throw new PluginException(e); } updateProperties(); - running = true; + state.setStarted(); loadSettings(callback.getSettings()); if (shouldAllowContactConnections()) { if (isAdapterEnabled()) bind(); @@ -172,7 +181,8 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { private void bind() { ioExecutor.execute(() -> { - if (!isRunning() || !shouldAllowContactConnections()) return; + if (!shouldAllowContactConnections() || getState() != ACTIVE) + return; // Bind a server socket to accept connections from contacts SS ss; try { @@ -181,14 +191,14 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { logException(LOG, WARNING, e); return; } - if (!isRunning() || !shouldAllowContactConnections()) { + if (!shouldAllowContactConnections() || + !state.setServerSocket(ss)) { + LOG.info("Closing redundant server socket"); tryToClose(ss); return; } - socket = ss; backoff.reset(); - callback.transportEnabled(); - acceptContactConnections(); + acceptContactConnections(ss); }); } @@ -217,34 +227,39 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { if (changed) callback.mergeLocalProperties(p); } - private void acceptContactConnections() { + private void acceptContactConnections(SS ss) { while (true) { DuplexTransportConnection conn; try { - conn = acceptConnection(socket); + conn = acceptConnection(ss); } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + // This is expected when the server socket is closed + LOG.info("Server socket closed"); + state.clearServerSocket(); return; } + LOG.info("Connection received"); backoff.reset(); if (connectionLimiter.contactConnectionOpened(conn)) callback.handleConnection(conn); - if (!running) return; } } @Override public void stop() { - running = false; - tryToClose(socket); - callback.transportDisabled(); + SS ss = state.setStopped(); + tryToClose(ss); disableAdapterIfEnabledByUs(); } @Override - public boolean isRunning() { - return running && isAdapterEnabled(); + public State getState() { + return state.getState(); + } + + @Override + public int getReasonDisabled() { + return getState() == DISABLED ? REASON_STARTING_STOPPING : -1; } @Override @@ -260,7 +275,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public void poll(Collection> properties) { - if (!isRunning() || !shouldAllowContactConnections()) return; + if (!shouldAllowContactConnections() || getState() != ACTIVE) return; backoff.increment(); for (Pair p : properties) { connect(p.getFirst(), p.getSecond()); @@ -273,7 +288,8 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { String uuid = p.get(PROP_UUID); if (isNullOrEmpty(uuid)) return; ioExecutor.execute(() -> { - if (!isRunning() || !shouldAllowContactConnections()) return; + if (!shouldAllowContactConnections() || getState() != ACTIVE) + return; if (!connectionLimiter.canOpenContactConnection()) return; DuplexTransportConnection d = createConnection(p); if (d != null) { @@ -317,7 +333,8 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createConnection(TransportProperties p) { - if (!isRunning() || !shouldAllowContactConnections()) return null; + if (!shouldAllowContactConnections() || getState() != ACTIVE) + return null; if (!connectionLimiter.canOpenContactConnection()) return null; String address = p.get(PROP_ADDRESS); if (isNullOrEmpty(address)) return null; @@ -336,7 +353,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { - if (!isRunning()) return null; + if (getState() != ACTIVE) return null; // No truncation necessary because COMMIT_LENGTH = 16 String uuid = UUID.nameUUIDFromBytes(commitment).toString(); if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); @@ -348,7 +365,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { logException(LOG, WARNING, e); return null; } - if (!isRunning()) { + if (getState() != ACTIVE) { tryToClose(ss); return null; } @@ -362,7 +379,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createKeyAgreementConnection( byte[] commitment, BdfList descriptor) { - if (!isRunning()) return null; + if (getState() != ACTIVE) return null; // No truncation necessary because COMMIT_LENGTH = 16 String uuid = UUID.nameUUIDFromBytes(commitment).toString(); DuplexTransportConnection conn; @@ -428,8 +445,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { boolean isAllowed = shouldAllowContactConnections(); if (wasAllowed && !isAllowed) { LOG.info("Contact connections disabled"); - tryToClose(socket); - callback.transportDisabled(); + tryToClose(state.clearServerSocket()); disableAdapterIfEnabledByUs(); } else if (!wasAllowed && isAllowed) { LOG.info("Contact connections enabled"); @@ -460,4 +476,48 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { tryToClose(ss); } } + + @ThreadSafe + @NotNullByDefault + protected class PluginState { + + @GuardedBy("this") + private boolean started = false, stopped = false; + @GuardedBy("this") + @Nullable + private SS serverSocket = null; + + synchronized void setStarted() { + started = true; + callback.pluginStateChanged(getState()); + } + + @Nullable + synchronized SS setStopped() { + stopped = true; + SS 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; + return ss; + } + + synchronized State getState() { + if (!started || stopped) return DISABLED; + return isAdapterEnabled() ? ACTIVE : INACTIVE; + } + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java index 0d4f56618..8a2673a7a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FilePlugin.java @@ -16,6 +16,7 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; 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.StringUtils.isNullOrEmpty; @@ -45,7 +46,7 @@ abstract class FilePlugin implements SimplexPlugin { @Override public TransportConnectionReader createReader(TransportProperties p) { - if (!isRunning()) return null; + if (getState() != ACTIVE) return null; String path = p.get(PROP_PATH); if (isNullOrEmpty(path)) return null; try { @@ -60,7 +61,7 @@ abstract class FilePlugin implements SimplexPlugin { @Override public TransportConnectionWriter createWriter(TransportProperties p) { - if (!isRunning()) return null; + if (getState() != ACTIVE) return null; String path = p.get(PROP_PATH); if (isNullOrEmpty(path)) return null; try { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java index 78ea1a7cd..c7a7d2e4b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java @@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.settings.Settings; -import org.briarproject.bramble.util.IoUtils; import java.io.IOException; import java.net.Inet4Address; @@ -19,7 +18,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Comparator; @@ -38,6 +36,7 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; +import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.join; @@ -149,8 +148,9 @@ class LanTcpPlugin extends TcpPlugin { if (remote.getPort() == 0) return false; if (!isAcceptableAddress(remote.getAddress())) return false; // Try to determine whether the address is on the same LAN as us - if (socket == null) return false; - byte[] localIp = socket.getInetAddress().getAddress(); + ServerSocket ss = state.getServerSocket(); + if (ss == null) return false; + byte[] localIp = ss.getInetAddress().getAddress(); byte[] remoteIp = remote.getAddress().getAddress(); return addressesAreOnSameLan(localIp, remoteIp); } @@ -209,10 +209,10 @@ class LanTcpPlugin extends TcpPlugin { } catch (IOException e) { if (LOG.isLoggable(INFO)) 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"); return null; } @@ -228,7 +228,8 @@ class LanTcpPlugin extends TcpPlugin { @Override public DuplexTransportConnection createKeyAgreementConnection( byte[] commitment, BdfList descriptor) { - if (!isRunning()) return null; + ServerSocket ss = state.getServerSocket(); + if (ss == null) return null; InetSocketAddress remote; try { remote = parseSocketAddress(descriptor); @@ -238,10 +239,9 @@ class LanTcpPlugin extends TcpPlugin { } if (!isConnectable(remote)) { if (LOG.isLoggable(INFO)) { - SocketAddress local = socket.getLocalSocketAddress(); LOG.info(scrubSocketAddress(remote) + " is not connectable from " + - scrubSocketAddress(local)); + scrubSocketAddress(ss.getLocalSocketAddress())); } return null; } @@ -249,7 +249,7 @@ class LanTcpPlugin extends TcpPlugin { if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + scrubSocketAddress(remote)); Socket s = createSocket(); - s.bind(new InetSocketAddress(socket.getInetAddress(), 0)); + s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.connect(remote); s.setSoTimeout(socketTimeout); if (LOG.isLoggable(INFO)) @@ -296,7 +296,7 @@ class LanTcpPlugin extends TcpPlugin { @Override public void close() { - IoUtils.tryToClose(ss, LOG, WARNING); + tryToClose(ss, LOG, WARNING); } } 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 4ca15d0b9..d4713fecf 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 @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.ConnectionHandler; @@ -14,7 +15,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; -import org.briarproject.bramble.util.IoUtils; import java.io.IOException; import java.net.InetAddress; @@ -22,7 +22,6 @@ import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; @@ -35,6 +34,8 @@ import java.util.logging.Logger; import java.util.regex.Pattern; 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; @@ -42,6 +43,10 @@ import static java.util.Collections.list; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; 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.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @@ -60,9 +65,7 @@ abstract class TcpPlugin implements DuplexPlugin { protected final PluginCallback callback; protected final int maxLatency, maxIdleTime, socketTimeout; protected final AtomicBoolean used = new AtomicBoolean(false); - - protected volatile boolean running = false; - protected volatile ServerSocket socket = null; + protected final PluginState state = new PluginState(); /** * Returns zero or more socket addresses on which the plugin should listen, @@ -86,6 +89,7 @@ abstract class TcpPlugin implements DuplexPlugin { /** * Returns true if connections to the given address can be attempted. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") protected abstract boolean isConnectable(InetSocketAddress remote); TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, @@ -115,14 +119,13 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public void start() { if (used.getAndSet(true)) throw new IllegalStateException(); - running = true; + state.setStarted(); bind(); } protected void bind() { bindExecutor.execute(() -> { - if (!running) return; - if (socket != null && !socket.isClosed()) return; + if (getState() != INACTIVE) return; ServerSocket ss = null; for (InetSocketAddress addr : getLocalSocketAddresses()) { try { @@ -132,34 +135,28 @@ abstract class TcpPlugin implements DuplexPlugin { } catch (IOException e) { if (LOG.isLoggable(INFO)) 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"); return; } - if (!running) { - tryToClose(ss); + if (!state.setServerSocket(ss)) { + LOG.info("Closing redundant server socket"); + tryToClose(ss, LOG, WARNING); return; } - socket = ss; backoff.reset(); InetSocketAddress local = (InetSocketAddress) ss.getLocalSocketAddress(); setLocalSocketAddress(local); if (LOG.isLoggable(INFO)) LOG.info("Listening on " + scrubSocketAddress(local)); - callback.transportEnabled(); - acceptContactConnections(); + acceptContactConnections(ss); }); } - protected void tryToClose(@Nullable ServerSocket ss) { - IoUtils.tryToClose(ss, LOG, WARNING); - callback.transportDisabled(); - } - String getIpPortString(InetSocketAddress a) { String addr = a.getAddress().getHostAddress(); int percent = addr.indexOf('%'); @@ -167,15 +164,16 @@ abstract class TcpPlugin implements DuplexPlugin { return addr + ":" + a.getPort(); } - private void acceptContactConnections() { - while (isRunning()) { + private void acceptContactConnections(ServerSocket ss) { + while (true) { Socket s; try { - s = socket.accept(); + s = ss.accept(); s.setSoTimeout(socketTimeout); } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + // This is expected when the server socket is closed + LOG.info("Server socket closed"); + state.clearServerSocket(ss); return; } if (LOG.isLoggable(INFO)) @@ -188,13 +186,18 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public void stop() { - running = false; - tryToClose(socket); + ServerSocket ss = state.setStopped(); + tryToClose(ss, LOG, WARNING); } @Override - public boolean isRunning() { - return running && socket != null && !socket.isClosed(); + public State getState() { + return state.getState(); + } + + @Override + public int getReasonDisabled() { + return getState() == DISABLED ? REASON_STARTING_STOPPING : -1; } @Override @@ -210,7 +213,7 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public void poll(Collection> properties) { - if (!isRunning()) return; + if (getState() != ACTIVE) return; backoff.increment(); for (Pair p : properties) { connect(p.getFirst(), p.getSecond()); @@ -229,14 +232,14 @@ abstract class TcpPlugin implements DuplexPlugin { @Override public DuplexTransportConnection createConnection(TransportProperties p) { - if (!isRunning()) return null; + ServerSocket ss = state.getServerSocket(); + if (ss == null) return null; for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { if (!isConnectable(remote)) { if (LOG.isLoggable(INFO)) { - SocketAddress local = socket.getLocalSocketAddress(); LOG.info(scrubSocketAddress(remote) + " is not connectable from " + - scrubSocketAddress(local)); + scrubSocketAddress(ss.getLocalSocketAddress())); } continue; } @@ -244,7 +247,7 @@ abstract class TcpPlugin implements DuplexPlugin { if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + scrubSocketAddress(remote)); Socket s = createSocket(); - s.bind(new InetSocketAddress(socket.getInetAddress(), 0)); + s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.connect(remote); s.setSoTimeout(socketTimeout); if (LOG.isLoggable(INFO)) @@ -327,4 +330,51 @@ abstract class TcpPlugin implements DuplexPlugin { return emptyList(); } } + + @ThreadSafe + @NotNullByDefault + protected class PluginState { + + @GuardedBy("this") + private boolean started = false, stopped = false; + @GuardedBy("this") + @Nullable + private ServerSocket serverSocket = null; + + synchronized void setStarted() { + started = true; + callback.pluginStateChanged(getState()); + } + + @Nullable + synchronized ServerSocket setStopped() { + stopped = true; + ServerSocket 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) return DISABLED; + return serverSocket == null ? INACTIVE : ACTIVE; + } + } } 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 e23a0da48..d20901002 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 @@ -15,6 +15,7 @@ import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.ConnectionHandler; @@ -54,6 +55,9 @@ import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; import javax.net.SocketFactory; 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_PRIVKEY; 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.ID; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; @@ -76,6 +84,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; @@ -113,16 +125,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final int maxLatency, maxIdleTime, socketTimeout; private final File torDirectory, torFile, geoIpFile, obfs4File, configFile; private final File doneFile, cookieFile; - private final ConnectionStatus connectionStatus; 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 TorControlConnection controlConnection = null; private volatile Settings settings = null; - protected volatile boolean running = false; - protected abstract int getProcessId(); protected abstract long getLastUpdateTime(); @@ -159,7 +169,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { configFile = new File(torDirectory, "torrc"); doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); - connectionStatus = new ConnectionStatus(); // Don't execute more than one connection status check at a time connectionStatusExecutor = new PoliteExecutor("TorPlugin", ioExecutor, 1); @@ -183,6 +192,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void start() throws PluginException { if (used.getAndSet(true)) throw new IllegalStateException(); + state.setStarted(); if (!torDirectory.exists()) { if (!torDirectory.mkdirs()) { LOG.warning("Could not create Tor directory."); @@ -258,7 +268,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { // Tell Tor to exit when the control connection is closed controlConnection.takeOwnership(); controlConnection.resetConf(singletonList(OWNER)); - running = true; // Register to receive events from the Tor process controlConnection.setEventHandler(this); controlConnection.setEvents(asList(EVENTS)); @@ -266,11 +275,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { String phase = controlConnection.getInfo("status/bootstrap-phase"); if (phase != null && phase.contains("PROGRESS=100")) { LOG.info("Tor has already bootstrapped"); - connectionStatus.setBootstrapped(); + state.setBootstrapped(); } } catch (IOException e) { throw new PluginException(e); } + state.setTorStarted(); // Check whether we're online updateConnectionStatus(networkManager.getNetworkStatus(), batteryManager.isCharging()); @@ -393,11 +403,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { tryToClose(ss, LOG, WARNING); return; } - if (!running) { + if (!state.setServerSocket(ss)) { + LOG.info("Closing redundant server socket"); tryToClose(ss, LOG, WARNING); return; } - socket = ss; // Store the port number String localPort = String.valueOf(ss.getLocalPort()); Settings s = new Settings(); @@ -412,7 +422,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } private void publishHiddenService(String port) { - if (!running) return; + if (!state.isTorRunning()) return; LOG.info("Creating hidden service"); String privKey = settings.get(HS_PRIVKEY); Map portLines = singletonMap(80, "127.0.0.1:" + port); @@ -450,14 +460,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } private void acceptContactConnections(ServerSocket ss) { - while (running) { + while (true) { Socket s; try { s = ss.accept(); s.setSoTimeout(socketTimeout); } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + // This is expected when the server socket is closed + LOG.info("Server socket closed"); + state.clearServerSocket(ss); return; } LOG.info("Connection received"); @@ -467,10 +478,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } protected void enableNetwork(boolean enable) throws IOException { - if (!running) return; - connectionStatus.enableNetwork(enable); + state.enableNetwork(enable); controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); - if (!enable) callback.transportDisabled(); } private void enableBridges(boolean enable, boolean needsMeek) @@ -494,9 +503,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void stop() { - running = false; - tryToClose(socket, LOG, WARNING); - callback.transportDisabled(); + ServerSocket ss = state.setStopped(); + tryToClose(ss, LOG, WARNING); if (controlSocket != null && controlConnection != null) { try { LOG.info("Stopping Tor"); @@ -510,8 +518,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public boolean isRunning() { - return running && connectionStatus.isConnected(); + public State getState() { + return state.getState(); + } + + @Override + public int getReasonDisabled() { + return state.getReasonDisabled(); } @Override @@ -527,7 +540,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void poll(Collection> properties) { - if (!isRunning()) return; + if (getState() != ACTIVE) return; backoff.increment(); for (Pair p : properties) { connect(p.getFirst(), p.getSecond()); @@ -546,7 +559,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public DuplexTransportConnection createConnection(TransportProperties p) { - if (!isRunning()) return null; + if (getState() != ACTIVE) return null; String bestOnion = null; String onion2 = p.get(PROP_ONION_V2); String onion3 = p.get(PROP_ONION_V3); @@ -634,8 +647,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { new TorTransportConnection(this, s)); } } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + // This is expected when the server socket is closed + LOG.info("Rendezvous server socket closed"); } }); Map portLines = @@ -663,10 +676,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void circuitStatus(String status, String id, String path) { if (status.equals("BUILT") && - connectionStatus.getAndSetCircuitBuilt()) { + state.getAndSetCircuitBuilt()) { LOG.info("First circuit built"); backoff.reset(); - if (isRunning()) callback.transportEnabled(); } } @@ -697,9 +709,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { public void message(String severity, String msg) { if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) { - connectionStatus.setBootstrapped(); + state.setBootstrapped(); backoff.reset(); - if (isRunning()) callback.transportEnabled(); } } @@ -736,7 +747,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private void disableNetwork() { connectionStatusExecutor.execute(() -> { try { - enableNetwork(false); + if (state.isTorRunning()) enableNetwork(false); } catch (IOException ex) { logException(LOG, WARNING, ex); } @@ -746,7 +757,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private void updateConnectionStatus(NetworkStatus status, boolean charging) { connectionStatusExecutor.execute(() -> { - if (!running) return; + if (!state.isTorRunning()) return; boolean online = status.isConnected(); boolean wifi = status.isWifi(); String country = locationUtils.getCurrentCountry(); @@ -762,47 +773,67 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (LOG.isLoggable(INFO)) { 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); LOG.info("Charging: " + charging); } - 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 || + 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 { + LOG.info("Enabling network"); + enableNetwork = true; + if (network == PREF_TOR_NETWORK_WITH_BRIDGES || (automatic && bridgesWork)) { if (circumventionProvider.needsMeek(country)) { - LOG.info("Enabling network, using meek bridges"); - enableBridges(true, true); + LOG.info("Using meek bridges"); + enableBridges = true; + useMeek = true; } else { - LOG.info("Enabling network, using obfs4 bridges"); - enableBridges(true, false); + LOG.info("Using obfs4 bridges"); + enableBridges = true; } - enableNetwork(true); } else { - LOG.info("Enabling network, not using bridges"); - enableBridges(false, false); - enableNetwork(true); + LOG.info("Not using bridges"); } - if (online && wifi && charging) { + if (wifi && charging) { LOG.info("Enabling connection padding"); - enableConnectionPadding(true); + enableConnectionPadding = true; } else { LOG.info("Disabling connection padding"); - enableConnectionPadding(false); } + } + + + state.setDisabledBySettings(disabledBySettings, reasonDisabled); + + try { + if (enableNetwork) { + enableBridges(enableBridges, useMeek); + enableConnectionPadding(enableConnectionPadding); + } + enableNetwork(enableNetwork); } catch (IOException e) { logException(LOG, WARNING, e); } @@ -810,33 +841,98 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } private void enableConnectionPadding(boolean enable) throws IOException { - if (!running) return; controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); } - private static class ConnectionStatus { + @ThreadSafe + @NotNullByDefault + protected class PluginState { - // All of the following are locking: this - private boolean networkEnabled = false; - private boolean bootstrapped = false, circuitBuilt = false; + @GuardedBy("this") + private boolean started = false, + stopped = false, + torStarted = false, + networkInitialised = false, + networkEnabled = false, + bootstrapped = false, + circuitBuilt = false, + disabledBySettings = false; - private synchronized void setBootstrapped() { - bootstrapped = true; + @GuardedBy("this") + 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() { + synchronized void setTorStarted() { + torStarted = true; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + 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; circuitBuilt = true; + callback.pluginStateChanged(getState()); return firstCircuit; } - private synchronized void enableNetwork(boolean enable) { + synchronized void enableNetwork(boolean enable) { + networkInitialised = true; networkEnabled = enable; if (!enable) circuitBuilt = false; + callback.pluginStateChanged(getState()); } - private synchronized boolean isConnected() { - return networkEnabled && bootstrapped && circuitBuilt; + synchronized void setDisabledBySettings(boolean disabledBySettings, + int reasonDisabled) { + this.disabledBySettings = disabledBySettings; + this.reasonDisabled = reasonDisabled; + callback.pluginStateChanged(getState()); + } + + synchronized boolean setServerSocket(ServerSocket ss) { + if (stopped || serverSocket != null) return false; + serverSocket = ss; + return true; + } + + 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; } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPollerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPollerImpl.java index d16317bff..4bce3d661 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPollerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPollerImpl.java @@ -31,8 +31,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; @@ -269,11 +269,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { } else if (e instanceof PendingContactRemovedEvent) { PendingContactRemovedEvent p = (PendingContactRemovedEvent) e; removePendingContactAsync(p.getId()); - } else if (e instanceof TransportEnabledEvent) { - TransportEnabledEvent t = (TransportEnabledEvent) e; + } else if (e instanceof TransportActiveEvent) { + TransportActiveEvent t = (TransportActiveEvent) e; addTransportAsync(t.getTransportId()); - } else if (e instanceof TransportDisabledEvent) { - TransportDisabledEvent t = (TransportDisabledEvent) e; + } else if (e instanceof TransportInactiveEvent) { + TransportInactiveEvent t = (TransportInactiveEvent) e; removeTransportAsync(t.getTransportId()); } else if (e instanceof RendezvousConnectionOpenedEvent) { RendezvousConnectionOpenedEvent r = diff --git a/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java index f03d4f730..228569fa2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java @@ -6,7 +6,7 @@ import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.DevReporter; import org.briarproject.bramble.util.IoUtils; @@ -92,8 +92,8 @@ class DevReporterImpl implements DevReporter, EventListener { @Override public void eventOccurred(Event e) { - if (e instanceof TransportEnabledEvent) { - TransportEnabledEvent t = (TransportEnabledEvent) e; + if (e instanceof TransportActiveEvent) { + TransportActiveEvent t = (TransportActiveEvent) e; if (t.getTransportId().equals(TorConstants.ID)) ioExecutor.execute(this::sendReports); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java index 255635a13..2cca5be44 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java @@ -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.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; @@ -322,7 +322,7 @@ public class PollerImplTest extends BrambleMockTestCase { } @Test - public void testPollsOnTransportEnabled() throws Exception { + public void testPollsOnTransportActivated() throws Exception { DuplexPlugin plugin = context.mock(DuplexPlugin.class); context.checking(new Expectations() {{ @@ -361,7 +361,7 @@ public class PollerImplTest extends BrambleMockTestCase { pairOf(equal(properties), any(ConnectionHandler.class))))); }}); - poller.eventOccurred(new TransportEnabledEvent(transportId)); + poller.eventOccurred(new TransportActiveEvent(transportId)); } @Test @@ -402,11 +402,11 @@ public class PollerImplTest extends BrambleMockTestCase { // All contacts are connected, so don't poll the plugin }}); - poller.eventOccurred(new TransportEnabledEvent(transportId)); + poller.eventOccurred(new TransportActiveEvent(transportId)); } @Test - public void testCancelsPollingOnTransportDisabled() { + public void testCancelsPollingOnTransportDeactivated() { Plugin plugin = context.mock(Plugin.class); context.checking(new Expectations() {{ @@ -424,11 +424,11 @@ public class PollerImplTest extends BrambleMockTestCase { oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L), with(MILLISECONDS)); 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); }}); - poller.eventOccurred(new TransportEnabledEvent(transportId)); - poller.eventOccurred(new TransportDisabledEvent(transportId)); + poller.eventOccurred(new TransportActiveEvent(transportId)); + poller.eventOccurred(new TransportInactiveEvent(transportId)); } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java index 12ccaf1e8..7b175f805 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; +import org.briarproject.bramble.api.plugin.Plugin.State; import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; @@ -327,6 +328,7 @@ public class LanTcpPluginTest extends BrambleTestCase { assertEquals(0, comparator.compare(linkLocal, linkLocal)); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean systemHasLocalIpv4Address() throws Exception { for (NetworkInterface i : list(getNetworkInterfaces())) { for (InetAddress a : list(i.getInetAddresses())) { @@ -365,11 +367,7 @@ public class LanTcpPluginTest extends BrambleTestCase { } @Override - public void transportEnabled() { - } - - @Override - public void transportDisabled() { + public void pluginStateChanged(State newState) { } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/rendezvous/RendezvousPollerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/rendezvous/RendezvousPollerImplTest.java index 0f65d9adf..2232b7c0e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/rendezvous/RendezvousPollerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/rendezvous/RendezvousPollerImplTest.java @@ -17,8 +17,8 @@ import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; @@ -178,10 +178,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { rendezvousPoller.startService(); context.assertIsSatisfied(); - // Enable the transport - no endpoints should be created yet + // Activate the transport - no endpoints should be created yet expectGetPlugin(); - rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId)); context.assertIsSatisfied(); // Add the pending contact - endpoint should be created and polled @@ -212,8 +212,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { new PendingContactRemovedEvent(pendingContact.getId())); context.assertIsSatisfied(); - // Disable the transport - endpoint is already closed - rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + // Deactivate the transport - endpoint is already closed + rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId)); } @Test @@ -230,10 +230,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { rendezvousPoller.startService(); context.assertIsSatisfied(); - // Enable the transport - no endpoints should be created yet + // Activate the transport - no endpoints should be created yet expectGetPlugin(); - rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId)); context.assertIsSatisfied(); // Add the pending contact - endpoint should be created and polled @@ -269,12 +269,12 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { new PendingContactRemovedEvent(pendingContact.getId())); context.assertIsSatisfied(); - // Disable the transport - endpoint is already closed - rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + // Deactivate the transport - endpoint is already closed + rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId)); } @Test - public void testCreatesAndClosesEndpointsWhenTransportIsEnabledAndDisabled() + public void testCreatesAndClosesEndpointsWhenTransportIsActivatedAndDeactivated() throws Exception { long beforeExpiry = pendingContact.getTimestamp(); @@ -292,19 +292,19 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase { new PendingContactAddedEvent(pendingContact)); context.assertIsSatisfied(); - // Enable the transport - endpoint should be created + // Activate the transport - endpoint should be created expectGetPlugin(); expectCreateEndpoint(); expectStateChangedEvent(WAITING_FOR_CONNECTION); - rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId)); context.assertIsSatisfied(); - // Disable the transport - endpoint should be closed + // Deactivate the transport - endpoint should be closed expectCloseEndpoint(); expectStateChangedEvent(OFFLINE); - rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + rendezvousPoller.eventOccurred(new TransportInactiveEvent(transportId)); context.assertIsSatisfied(); // Remove the pending contact - endpoint is already closed 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 16a0d0f31..e2eb69ca8 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 @@ -4,6 +4,7 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.PluginCallback; @@ -23,9 +24,16 @@ import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; 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.WARNING; 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.StringUtils.isNullOrEmpty; @@ -44,8 +52,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { private final PluginCallback callback; private final int maxLatency; private final AtomicBoolean used = new AtomicBoolean(false); + private final PluginState state = new PluginState(); - private volatile boolean running = false; private volatile Modem modem = null; ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList, @@ -75,6 +83,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { @Override public void start() throws PluginException { if (used.getAndSet(true)) throw new IllegalStateException(); + state.setStarted(); for (String portName : serialPortList.getPortNames()) { if (LOG.isLoggable(INFO)) LOG.info("Trying to initialise modem on " + portName); @@ -83,18 +92,20 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { if (!modem.start()) continue; if (LOG.isLoggable(INFO)) LOG.info("Initialised modem on " + portName); - running = true; + state.setInitialised(); return; } catch (IOException e) { logException(LOG, WARNING, e); } } + LOG.warning("Failed to initialised modem"); + state.setFailed(); throw new PluginException(); } @Override public void stop() { - running = false; + state.setStopped(); if (modem != null) { try { modem.stop(); @@ -105,8 +116,13 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { } @Override - public boolean isRunning() { - return running; + public State getState() { + return state.getState(); + } + + @Override + public int getReasonDisabled() { + return getState() == DISABLED ? REASON_STARTING_STOPPING : -1; } @Override @@ -125,8 +141,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { throw new UnsupportedOperationException(); } - private boolean resetModem() { - if (!running) return false; + private void resetModem() { + if (getState() != ACTIVE) return; for (String portName : serialPortList.getPortNames()) { if (LOG.isLoggable(INFO)) LOG.info("Trying to initialise modem on " + portName); @@ -135,18 +151,18 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { if (!modem.start()) continue; if (LOG.isLoggable(INFO)) LOG.info("Initialised modem on " + portName); - return true; + return; } catch (IOException e) { logException(LOG, WARNING, e); } } - running = false; - return false; + LOG.warning("Failed to initialise modem"); + state.setFailed(); } @Override public DuplexTransportConnection createConnection(TransportProperties p) { - if (!running) return null; + if (getState() != ACTIVE) return null; // Get the ISO 3166 code for the caller's country String fromIso = callback.getLocalProperties().get("iso3166"); if (isNullOrEmpty(fromIso)) return null; @@ -232,4 +248,41 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { 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; + } + } } diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java index df03d3abf..4e3ae0684 100644 --- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java +++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/modem/ModemPluginTest.java @@ -9,6 +9,8 @@ import org.junit.Test; 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.assertNull; @@ -33,6 +35,7 @@ public class ModemPluginTest extends BrambleMockTestCase { @Test public void testModemCreation() throws Exception { context.checking(new Expectations() {{ + oneOf(callback).pluginStateChanged(ENABLING); oneOf(serialPortList).getPortNames(); will(returnValue(new String[] {"foo", "bar", "baz"})); // First call to createModem() returns false @@ -50,6 +53,7 @@ public class ModemPluginTest extends BrambleMockTestCase { will(returnValue(modem)); oneOf(modem).start(); will(returnValue(true)); + oneOf(callback).pluginStateChanged(ACTIVE); }}); plugin.start(); @@ -65,12 +69,14 @@ public class ModemPluginTest extends BrambleMockTestCase { context.checking(new Expectations() {{ // start() + oneOf(callback).pluginStateChanged(ENABLING); oneOf(serialPortList).getPortNames(); will(returnValue(new String[] {"foo"})); oneOf(modemFactory).createModem(plugin, "foo"); will(returnValue(modem)); oneOf(modem).start(); will(returnValue(true)); + oneOf(callback).pluginStateChanged(ACTIVE); // createConnection() oneOf(callback).getLocalProperties(); will(returnValue(local)); @@ -93,12 +99,14 @@ public class ModemPluginTest extends BrambleMockTestCase { context.checking(new Expectations() {{ // start() + oneOf(callback).pluginStateChanged(ENABLING); oneOf(serialPortList).getPortNames(); will(returnValue(new String[] {"foo"})); oneOf(modemFactory).createModem(plugin, "foo"); will(returnValue(modem)); oneOf(modem).start(); will(returnValue(true)); + oneOf(callback).pluginStateChanged(ACTIVE); // createConnection() oneOf(callback).getLocalProperties(); will(returnValue(local)); @@ -121,12 +129,14 @@ public class ModemPluginTest extends BrambleMockTestCase { context.checking(new Expectations() {{ // start() + oneOf(callback).pluginStateChanged(ENABLING); oneOf(serialPortList).getPortNames(); will(returnValue(new String[] {"foo"})); oneOf(modemFactory).createModem(plugin, "foo"); will(returnValue(modem)); oneOf(modem).start(); will(returnValue(true)); + oneOf(callback).pluginStateChanged(ACTIVE); // createConnection() oneOf(callback).getLocalProperties(); will(returnValue(local)); diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java index b0f47bd86..7439321fd 100644 --- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java +++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java @@ -32,6 +32,7 @@ import javax.net.SocketFactory; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.SECONDS; 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.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled; @@ -141,10 +142,10 @@ public class BridgeTest extends BrambleTestCase { plugin.start(); long start = clock.currentTimeMillis(); while (clock.currentTimeMillis() - start < TIMEOUT) { - if (plugin.isRunning()) return; + if (plugin.getState() == ACTIVE) return; clock.sleep(500); } - if (!plugin.isRunning()) { + if (plugin.getState() != ACTIVE) { fail("Could not connect to Tor within timeout."); } } finally { diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/TestPluginCallback.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/TestPluginCallback.java index e8b8121da..aeb5a9ee9 100644 --- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/TestPluginCallback.java +++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/TestPluginCallback.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.plugin.tor; 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.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; @@ -30,11 +31,7 @@ public class TestPluginCallback implements PluginCallback { } @Override - public void transportEnabled() { - } - - @Override - public void transportDisabled() { + public void pluginStateChanged(State state) { } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java index 4af7eded3..b3488804e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerControllerImpl.java @@ -14,8 +14,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; +import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.android.controller.DbControllerImpl; @@ -30,6 +30,7 @@ import static java.util.concurrent.TimeUnit.DAYS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; @@ -85,16 +86,16 @@ public class NavDrawerControllerImpl extends DbControllerImpl @Override public void eventOccurred(Event e) { - if (e instanceof TransportEnabledEvent) { - TransportId id = ((TransportEnabledEvent) e).getTransportId(); + if (e instanceof TransportActiveEvent) { + TransportId id = ((TransportActiveEvent) e).getTransportId(); if (LOG.isLoggable(INFO)) { - LOG.info("TransportEnabledEvent: " + id.getString()); + LOG.info("TransportActiveEvent: " + id.getString()); } listener.stateUpdate(id, true); - } else if (e instanceof TransportDisabledEvent) { - TransportId id = ((TransportDisabledEvent) e).getTransportId(); + } else if (e instanceof TransportInactiveEvent) { + TransportId id = ((TransportInactiveEvent) e).getTransportId(); if (LOG.isLoggable(INFO)) { - LOG.info("TransportDisabledEvent: " + id.getString()); + LOG.info("TransportInactiveEvent: " + id.getString()); } listener.stateUpdate(id, false); } @@ -176,7 +177,7 @@ public class NavDrawerControllerImpl extends DbControllerImpl @Override public boolean isTransportRunning(TransportId transportId) { Plugin plugin = pluginManager.getPlugin(transportId); - return plugin != null && plugin.isRunning(); + return plugin != null && plugin.getState() == ACTIVE; } } diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java index 04350c8a6..b7dfa5783 100644 --- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedManagerImpl.java @@ -24,7 +24,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.system.Clock; @@ -120,8 +120,8 @@ class FeedManagerImpl implements FeedManager, EventListener, OpenDatabaseHook, @Override public void eventOccurred(Event e) { - if (e instanceof TransportEnabledEvent) { - TransportId t = ((TransportEnabledEvent) e).getTransportId(); + if (e instanceof TransportActiveEvent) { + TransportId t = ((TransportActiveEvent) e).getTransportId(); if (t.equals(TorConstants.ID)) { startFeedExecutor(); }