From 3181b695df6369605de42fead2d66d0051dd2a64 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 24 May 2018 13:15:38 +0100 Subject: [PATCH] Remove RemovableDrivePlugin, refactor plugin interface. --- .../bramble/plugin/tor/TorPlugin.java | 22 +- .../bramble/api/plugin/FileConstants.java | 6 + .../bramble/api/plugin/Plugin.java | 17 +- .../bramble/api/plugin/PluginCallback.java | 41 +- .../api/plugin/TransportConnectionWriter.java | 5 - .../AbstractDuplexTransportConnection.java | 5 - .../api/plugin/duplex/DuplexPlugin.java | 9 +- .../plugin/duplex/DuplexPluginCallback.java | 3 +- .../api/plugin/simplex/SimplexPlugin.java | 16 +- .../plugin/simplex/SimplexPluginCallback.java | 4 +- .../bramble/api/ui/UiCallback.java | 26 -- .../briarproject/bramble/util/OsUtils.java | 13 - .../bramble/plugin/PluginManagerImpl.java | 47 +-- .../bramble/plugin/PluginModule.java | 8 - .../briarproject/bramble/plugin/Poller.java | 39 +- .../plugin/bluetooth/BluetoothPlugin.java | 13 +- .../bramble/plugin/file/FilePlugin.java | 119 ++---- .../plugin/file/FileTransportReader.java | 5 +- .../plugin/file/FileTransportWriter.java | 13 +- .../bramble/plugin/tcp/TcpPlugin.java | 17 +- .../bramble/plugin/PluginManagerImplTest.java | 16 +- .../bramble/plugin/PollerTest.java | 126 ++++-- .../bramble/plugin/tcp/LanTcpPluginTest.java | 41 +- .../bramble/test/CaptureArgumentAction.java | 2 +- .../bramble/test/TestPluginConfigModule.java | 7 +- bramble-j2se/libs/jnotify-0.94.jar | Bin 30754 -> 0 bytes bramble-j2se/libs/jnotify-x86.dll | Bin 949426 -> 0 bytes bramble-j2se/libs/jnotify-x86_64.dll | Bin 1203750 -> 0 bytes bramble-j2se/libs/libjnotify-amd64.so | Bin 10304 -> 0 bytes bramble-j2se/libs/libjnotify-i386.so | Bin 9540 -> 0 bytes bramble-j2se/libs/libjnotify.dylib | Bin 67092 -> 0 bytes .../bramble/plugin/DesktopPluginModule.java | 14 +- .../file/LinuxRemovableDriveFinder.java | 28 -- .../file/LinuxRemovableDriveMonitor.java | 12 - .../plugin/file/MacRemovableDriveFinder.java | 28 -- .../plugin/file/MacRemovableDriveMonitor.java | 12 - .../file/PollingRemovableDriveMonitor.java | 84 ---- .../plugin/file/RemovableDriveFinder.java | 13 - .../plugin/file/RemovableDriveMonitor.java | 21 - .../plugin/file/RemovableDrivePlugin.java | 140 ------- .../file/RemovableDrivePluginFactory.java | 63 --- .../plugin/file/UnixRemovableDriveFinder.java | 48 --- .../file/UnixRemovableDriveMonitor.java | 129 ------ .../file/WindowsRemovableDriveFinder.java | 34 -- .../bramble/plugin/modem/ModemPlugin.java | 11 +- .../file/LinuxRemovableDriveFinderTest.java | 26 -- .../file/MacRemovableDriveFinderTest.java | 24 -- .../PollingRemovableDriveMonitorTest.java | 107 ----- .../plugin/file/RemovableDrivePluginTest.java | 378 ------------------ .../file/UnixRemovableDriveMonitorTest.java | 112 ------ .../bramble/plugin/modem/ModemPluginTest.java | 16 +- .../briarproject/briar/android/AppModule.java | 40 +- 52 files changed, 250 insertions(+), 1710 deletions(-) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java delete mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java delete mode 100644 bramble-j2se/libs/jnotify-0.94.jar delete mode 100644 bramble-j2se/libs/jnotify-x86.dll delete mode 100644 bramble-j2se/libs/jnotify-x86_64.dll delete mode 100644 bramble-j2se/libs/libjnotify-amd64.so delete mode 100644 bramble-j2se/libs/libjnotify-i386.so delete mode 100644 bramble-j2se/libs/libjnotify.dylib delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinder.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveMonitor.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinder.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/MacRemovableDriveMonitor.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitor.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveFinder.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDriveMonitor.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePlugin.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginFactory.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveFinder.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitor.java delete mode 100644 bramble-j2se/src/main/java/org/briarproject/bramble/plugin/file/WindowsRemovableDriveFinder.java delete mode 100644 bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/LinuxRemovableDriveFinderTest.java delete mode 100644 bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/MacRemovableDriveFinderTest.java delete mode 100644 bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/PollingRemovableDriveMonitorTest.java delete mode 100644 bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/RemovableDrivePluginTest.java delete mode 100644 bramble-j2se/src/test/java/org/briarproject/bramble/plugin/file/UnixRemovableDriveMonitorTest.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index b0ccdab86..5ce2d8255 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -50,7 +50,6 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -334,7 +333,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { return zin; } - private InputStream getConfigInputStream() throws IOException { + private InputStream getConfigInputStream() { int resId = getResourceId("torrc"); return appContext.getResources().openRawResource(resId); } @@ -499,7 +498,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public void stop() throws PluginException { + public void stop() { running = false; tryToClose(socket); if (networkStateReceiver != null) @@ -533,20 +532,16 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public void poll(Collection connected) { + public void poll(Map contacts) { if (!isRunning()) return; backoff.increment(); - Map remote = - callback.getRemoteProperties(); - for (Entry e : remote.entrySet()) { - ContactId c = e.getKey(); - if (!connected.contains(c)) connectAndCallBack(c, e.getValue()); + for (Entry e : contacts.entrySet()) { + connectAndCallBack(e.getKey(), e.getValue()); } } private void connectAndCallBack(ContactId c, TransportProperties p) { ioExecutor.execute(() -> { - if (!isRunning()) return; DuplexTransportConnection d = createConnection(p); if (d != null) { backoff.reset(); @@ -556,13 +551,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public DuplexTransportConnection createConnection(ContactId c) { + public DuplexTransportConnection createConnection(TransportProperties p) { if (!isRunning()) return null; - return createConnection(callback.getRemoteProperties(c)); - } - - @Nullable - private DuplexTransportConnection createConnection(TransportProperties p) { String onion = p.get(PROP_ONION); if (StringUtils.isNullOrEmpty(onion)) return null; if (!ONION.matcher(onion).matches()) { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java new file mode 100644 index 000000000..bed296874 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/FileConstants.java @@ -0,0 +1,6 @@ +package org.briarproject.bramble.api.plugin; + +public interface FileConstants { + + String PROP_PATH = "path"; +} 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 f37ef3f07..9e3fdd466 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 @@ -2,8 +2,9 @@ package org.briarproject.bramble.api.plugin; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.properties.TransportProperties; -import java.util.Collection; +import java.util.Map; @NotNullByDefault public interface Plugin { @@ -39,21 +40,19 @@ public interface Plugin { boolean isRunning(); /** - * Returns true if the plugin's {@link #poll(Collection)} method should be - * called periodically to attempt to establish connections. + * Returns true if the plugin should be polled periodically to attempt to + * establish connections. */ boolean shouldPoll(); /** - * Returns the desired interval in milliseconds between calls to the - * plugin's {@link #poll(Collection)} method. + * Returns the desired interval in milliseconds between polling attempts. */ int getPollingInterval(); /** - * Attempts to establish connections to contacts, passing any created - * connections to the callback. To avoid creating redundant connections, - * the plugin may exclude the given contacts from polling. + * Attempts to establish connections to the given contacts, passing any + * created connections to the callback. */ - void poll(Collection connected); + void poll(Map contacts); } 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 3b7364c4f..f2d5e8f79 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,12 +1,9 @@ package org.briarproject.bramble.api.plugin; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.settings.Settings; -import java.util.Map; - /** * An interface through which a transport plugin interacts with the rest of * the application. @@ -25,17 +22,7 @@ public interface PluginCallback { TransportProperties getLocalProperties(); /** - * Returns the plugin's remote transport properties. - */ - Map getRemoteProperties(); - - /** - * Returns the plugin's remote transport properties for the given contact. - */ - TransportProperties getRemoteProperties(ContactId c); - - /** - * Merges the given settings with the namespaced settings + * Merges the given settings with the plugin's settings */ void mergeSettings(Settings s); @@ -45,34 +32,12 @@ public interface PluginCallback { void mergeLocalProperties(TransportProperties p); /** - * Presents the user with a choice among two or more named options and - * returns the user's response. The message may consist of a translatable - * format string and arguments. - * - * @return an index into the array of options indicating the user's choice, - * or -1 if the user cancelled the choice. - */ - int showChoice(String[] options, String... message); - - /** - * Asks the user to confirm an action and returns the user's response. The - * message may consist of a translatable format string and arguments. - */ - boolean showConfirmationMessage(String... message); - - /** - * Shows a message to the user. The message may consist of a translatable - * format string and arguments. - */ - void showMessage(String... message); - - /** - * Signal that the transport got enabled. + * Signals that the transport is enabled. */ void transportEnabled(); /** - * Signal that the transport got disabled. + * Signals that the transport is disabled. */ void transportDisabled(); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java index a066f6422..219f33efe 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/TransportConnectionWriter.java @@ -22,11 +22,6 @@ public interface TransportConnectionWriter { */ int getMaxIdleTime(); - /** - * Returns the capacity of the transport connection in bytes. - */ - long getCapacity(); - /** * Returns an output stream for writing to the transport connection. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java index cdad52612..ba84134e9 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractDuplexTransportConnection.java @@ -71,11 +71,6 @@ public abstract class AbstractDuplexTransportConnection return plugin.getMaxIdleTime(); } - @Override - public long getCapacity() { - return Long.MAX_VALUE; - } - @Override public OutputStream getOutputStream() throws IOException { return AbstractDuplexTransportConnection.this.getOutputStream(); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java index 8ab6c4fe4..5633d77ee 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPlugin.java @@ -1,10 +1,10 @@ package org.briarproject.bramble.api.plugin.duplex; -import org.briarproject.bramble.api.contact.ContactId; 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.Plugin; +import org.briarproject.bramble.api.properties.TransportProperties; import javax.annotation.Nullable; @@ -15,12 +15,11 @@ import javax.annotation.Nullable; public interface DuplexPlugin extends Plugin { /** - * Attempts to create and return a connection to the given contact using - * the current transport and configuration properties. Returns null if a - * connection cannot be created. + * Attempts to create and return a connection using the given transport + * properties. Returns null if a connection cannot be created. */ @Nullable - DuplexTransportConnection createConnection(ContactId c); + DuplexTransportConnection createConnection(TransportProperties p); /** * Returns true if the plugin supports short-range key agreement. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java index 00f1acdc6..8a97cf7fb 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/DuplexPluginCallback.java @@ -5,7 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.PluginCallback; /** - * An interface for handling connections created by a duplex transport plugin. + * An interface through which a duplex plugin interacts with the rest of the + * application. */ @NotNullByDefault public interface DuplexPluginCallback extends PluginCallback { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java index 3778918c2..7f0ab0141 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPlugin.java @@ -1,10 +1,10 @@ package org.briarproject.bramble.api.plugin.simplex; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; +import org.briarproject.bramble.api.properties.TransportProperties; import javax.annotation.Nullable; @@ -15,18 +15,16 @@ import javax.annotation.Nullable; public interface SimplexPlugin extends Plugin { /** - * Attempts to create and return a reader for the given contact using the - * current transport and configuration properties. Returns null if a reader - * cannot be created. + * Attempts to create and return a reader for the given transport + * properties. Returns null if a reader cannot be created. */ @Nullable - TransportConnectionReader createReader(ContactId c); + TransportConnectionReader createReader(TransportProperties p); /** - * Attempts to create and return a writer for the given contact using the - * current transport and configuration properties. Returns null if a writer - * cannot be created. + * Attempts to create and return a writer for the given transport + * properties. Returns null if a writer cannot be created. */ @Nullable - TransportConnectionWriter createWriter(ContactId c); + TransportConnectionWriter createWriter(TransportProperties p); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java index d36a5d5e8..1f07ec25a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/simplex/SimplexPluginCallback.java @@ -7,8 +7,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; /** - * An interface for handling readers and writers created by a simplex transport - * plugin. + * An interface through which a simplex plugin interacts with the rest of the + * application. */ @NotNullByDefault public interface SimplexPluginCallback extends PluginCallback { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java b/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java deleted file mode 100644 index c5ad6d1fc..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/ui/UiCallback.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.briarproject.bramble.api.ui; - -public interface UiCallback { - - /** - * Presents the user with a choice among two or more named options and - * returns the user's response. The message may consist of a translatable - * format string and arguments. - * - * @return an index into the array of options indicating the user's choice, - * or -1 if the user cancelled the choice. - */ - int showChoice(String[] options, String... message); - - /** - * Asks the user to confirm an action and returns the user's response. The - * message may consist of a translatable format string and arguments. - */ - boolean showConfirmationMessage(String... message); - - /** - * Shows a message to the user. The message may consist of a translatable - * format string and arguments. - */ - void showMessage(String... message); -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java index 66ee8f256..bcde55bf0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/util/OsUtils.java @@ -22,19 +22,6 @@ public class OsUtils { return os != null && os.contains("Mac OS"); } - public static boolean isMacLeopardOrNewer() { - if (!isMac() || version == null) return false; - try { - String[] v = version.split("\\."); - if (v.length != 3) return false; - int major = Integer.parseInt(v[0]); - int minor = Integer.parseInt(v[1]); - return major >= 10 && minor >= 5; - } catch (NumberFormatException e) { - return false; - } - } - public static boolean isLinux() { return os != null && os.contains("Linux") && !isAndroid(); } 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 5b74eb4be..65f66345c 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 @@ -32,12 +32,10 @@ import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Scheduler; -import org.briarproject.bramble.api.ui.UiCallback; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -71,7 +69,6 @@ class PluginManagerImpl implements PluginManager, Service { private final TransportPropertyManager transportPropertyManager; private final SecureRandom random; private final Clock clock; - private final UiCallback uiCallback; private final Map plugins; private final List simplexPlugins; private final List duplexPlugins; @@ -85,8 +82,7 @@ class PluginManagerImpl implements PluginManager, Service { ConnectionRegistry connectionRegistry, SettingsManager settingsManager, TransportPropertyManager transportPropertyManager, - SecureRandom random, Clock clock, - UiCallback uiCallback) { + SecureRandom random, Clock clock) { this.ioExecutor = ioExecutor; this.scheduler = scheduler; this.eventBus = eventBus; @@ -97,7 +93,6 @@ class PluginManagerImpl implements PluginManager, Service { this.transportPropertyManager = transportPropertyManager; this.random = random; this.clock = clock; - this.uiCallback = uiCallback; plugins = new ConcurrentHashMap<>(); simplexPlugins = new CopyOnWriteArrayList<>(); duplexPlugins = new CopyOnWriteArrayList<>(); @@ -106,13 +101,14 @@ class PluginManagerImpl implements PluginManager, Service { } @Override - public void startService() throws ServiceException { + public void startService() { if (used.getAndSet(true)) throw new IllegalStateException(); // Instantiate the poller if (pluginConfig.shouldPoll()) { LOG.info("Starting poller"); Poller poller = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, this, random, clock); + connectionRegistry, this, transportPropertyManager, random, + clock); eventBus.addListener(poller); } // Instantiate the simplex plugins and start them asynchronously @@ -297,26 +293,6 @@ class PluginManagerImpl implements PluginManager, Service { } } - @Override - public Map getRemoteProperties() { - try { - return transportPropertyManager.getRemoteProperties(id); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return Collections.emptyMap(); - } - } - - @Override - public TransportProperties getRemoteProperties(ContactId c) { - try { - return transportPropertyManager.getRemoteProperties(c, id); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return new TransportProperties(); - } - } - @Override public void mergeSettings(Settings s) { try { @@ -335,21 +311,6 @@ class PluginManagerImpl implements PluginManager, Service { } } - @Override - public int showChoice(String[] options, String... message) { - return uiCallback.showChoice(options, message); - } - - @Override - public boolean showConfirmationMessage(String... message) { - return uiCallback.showConfirmationMessage(message); - } - - @Override - public void showMessage(String... message) { - uiCallback.showMessage(message); - } - @Override public void transportEnabled() { eventBus.broadcast(new TransportEnabledEvent(id)); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java index 773910481..499da88b9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginModule.java @@ -1,18 +1,10 @@ package org.briarproject.bramble.plugin; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.PluginManager; -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.bramble.api.system.Scheduler; - -import java.security.SecureRandom; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java index c68126282..a089fe203 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/Poller.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent; +import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.lifecycle.IoExecutor; @@ -19,10 +20,13 @@ 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.simplex.SimplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Scheduler; import java.security.SecureRandom; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; @@ -33,10 +37,10 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; import javax.annotation.concurrent.ThreadSafe; -import javax.inject.Inject; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; @ThreadSafe @NotNullByDefault @@ -49,6 +53,7 @@ class Poller implements EventListener { private final ConnectionManager connectionManager; private final ConnectionRegistry connectionRegistry; private final PluginManager pluginManager; + private final TransportPropertyManager transportPropertyManager; private final SecureRandom random; private final Clock clock; private final Lock lock; @@ -58,12 +63,14 @@ class Poller implements EventListener { @Scheduler ScheduledExecutorService scheduler, ConnectionManager connectionManager, ConnectionRegistry connectionRegistry, PluginManager pluginManager, + TransportPropertyManager transportPropertyManager, SecureRandom random, Clock clock) { this.ioExecutor = ioExecutor; this.scheduler = scheduler; this.connectionManager = connectionManager; this.connectionRegistry = connectionRegistry; this.pluginManager = pluginManager; + this.transportPropertyManager = transportPropertyManager; this.random = random; this.clock = clock; lock = new ReentrantLock(); @@ -119,10 +126,15 @@ class Poller implements EventListener { private void connectToContact(ContactId c, SimplexPlugin p) { ioExecutor.execute(() -> { TransportId t = p.getId(); - if (!connectionRegistry.isConnected(c, t)) { - TransportConnectionWriter w = p.createWriter(c); + if (connectionRegistry.isConnected(c, t)) return; + try { + TransportProperties props = + transportPropertyManager.getRemoteProperties(c, t); + TransportConnectionWriter w = p.createWriter(props); if (w != null) connectionManager.manageOutgoingConnection(c, t, w); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } }); } @@ -130,10 +142,15 @@ class Poller implements EventListener { private void connectToContact(ContactId c, DuplexPlugin p) { ioExecutor.execute(() -> { TransportId t = p.getId(); - if (!connectionRegistry.isConnected(c, t)) { - DuplexTransportConnection d = p.createConnection(c); + if (connectionRegistry.isConnected(c, t)) return; + try { + TransportProperties props = + transportPropertyManager.getRemoteProperties(c, t); + DuplexTransportConnection d = p.createConnection(props); if (d != null) connectionManager.manageOutgoingConnection(c, t, d); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } }); } @@ -185,7 +202,17 @@ class Poller implements EventListener { private void poll(Plugin p) { TransportId t = p.getId(); if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t); - p.poll(connectionRegistry.getConnectedContacts(t)); + try { + Map remote = + transportPropertyManager.getRemoteProperties(t); + Collection connected = + connectionRegistry.getConnectedContacts(t); + remote = new HashMap<>(remote); + remote.keySet().removeAll(connected); + if (!remote.isEmpty()) p.poll(remote); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } } private class ScheduledPollTask { 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 9b4ba5bb9..14ee2f2bf 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 @@ -26,7 +26,6 @@ import org.briarproject.bramble.util.StringUtils; import java.io.IOException; import java.security.SecureRandom; -import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; @@ -250,19 +249,16 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { } @Override - public void poll(Collection connected) { + public void poll(Map contacts) { if (!isRunning() || !shouldAllowContactConnections()) return; backoff.increment(); // Try to connect to known devices in parallel - Map remote = - callback.getRemoteProperties(); - for (Entry e : remote.entrySet()) { - ContactId c = e.getKey(); - if (connected.contains(c)) continue; + for (Entry e : contacts.entrySet()) { String address = e.getValue().get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) continue; String uuid = e.getValue().get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) continue; + ContactId c = e.getKey(); ioExecutor.execute(() -> { if (!isRunning() || !shouldAllowContactConnections()) return; if (!connectionLimiter.canOpenContactConnection()) return; @@ -308,10 +304,9 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { } @Override - public DuplexTransportConnection createConnection(ContactId c) { + public DuplexTransportConnection createConnection(TransportProperties p) { if (!isRunning() || !shouldAllowContactConnections()) return null; if (!connectionLimiter.canOpenContactConnection()) return null; - TransportProperties p = callback.getRemoteProperties(c); String address = p.get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) return null; String uuid = p.get(PROP_UUID); 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 6355342e6..5aaa9f012 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 @@ -1,27 +1,21 @@ package org.briarproject.bramble.plugin.file; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback; +import org.briarproject.bramble.api.properties.TransportProperties; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.util.Collection; -import java.util.Locale; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; -import javax.annotation.Nullable; - import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.api.transport.TransportConstants.MIN_STREAM_LENGTH; +import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH; +import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @NotNullByDefault abstract class FilePlugin implements SimplexPlugin { @@ -29,25 +23,15 @@ abstract class FilePlugin implements SimplexPlugin { private static final Logger LOG = Logger.getLogger(FilePlugin.class.getName()); - protected final Executor ioExecutor; protected final SimplexPluginCallback callback; protected final int maxLatency; - protected final AtomicBoolean used = new AtomicBoolean(false); - protected volatile boolean running = false; + protected abstract void writerFinished(File f, boolean exception); - @Nullable - protected abstract File chooseOutputDirectory(); + protected abstract void readerFinished(File f, boolean exception, + boolean recognised); - protected abstract Collection findFilesByName(String filename); - - protected abstract void writerFinished(File f); - - protected abstract void readerFinished(File f); - - protected FilePlugin(Executor ioExecutor, SimplexPluginCallback callback, - int maxLatency) { - this.ioExecutor = ioExecutor; + FilePlugin(SimplexPluginCallback callback, int maxLatency) { this.callback = callback; this.maxLatency = maxLatency; } @@ -58,81 +42,36 @@ abstract class FilePlugin implements SimplexPlugin { } @Override - public int getMaxIdleTime() { - return Integer.MAX_VALUE; // We don't need keepalives - } - - @Override - public boolean isRunning() { - return running; - } - - @Override - public TransportConnectionReader createReader(ContactId c) { - return null; - } - - @Override - public TransportConnectionWriter createWriter(ContactId c) { - if (!running) return null; - return createWriter(createConnectionFilename()); - } - - private String createConnectionFilename() { - StringBuilder s = new StringBuilder(12); - for (int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26)); - s.append(".dat"); - return s.toString(); - } - - // Package access for testing - boolean isPossibleConnectionFilename(String filename) { - return filename.toLowerCase(Locale.US).matches("[a-z]{8}\\.dat"); - } - - @Nullable - private TransportConnectionWriter createWriter(String filename) { - if (!running) return null; - File dir = chooseOutputDirectory(); - if (dir == null || !dir.exists() || !dir.isDirectory()) return null; - File f = new File(dir, filename); + public TransportConnectionReader createReader(TransportProperties p) { + if (!isRunning()) return null; + String path = p.get(PROP_PATH); + if (isNullOrEmpty(path)) return null; try { - long capacity = dir.getFreeSpace(); - if (capacity < MIN_STREAM_LENGTH) return null; - OutputStream out = new FileOutputStream(f); - return new FileTransportWriter(f, out, capacity, this); + File file = new File(path); + FileInputStream in = new FileInputStream(file); + return new FileTransportReader(file, in, this); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - f.delete(); return null; } } - protected void createReaderFromFile(File f) { - if (!running) return; - ioExecutor.execute(new ReaderCreator(f)); - } - - private class ReaderCreator implements Runnable { - - private final File file; - - private ReaderCreator(File file) { - this.file = file; - } - - @Override - public void run() { - if (isPossibleConnectionFilename(file.getName())) { - try { - FileInputStream in = new FileInputStream(file); - callback.readerCreated(new FileTransportReader(file, in, - FilePlugin.this)); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } + @Override + public TransportConnectionWriter createWriter(TransportProperties p) { + if (!isRunning()) return null; + String path = p.get(PROP_PATH); + if (isNullOrEmpty(path)) return null; + try { + File file = new File(path); + if (!file.exists() && !file.createNewFile()) { + LOG.info("Failed to create file"); + return null; } + FileOutputStream out = new FileOutputStream(file); + return new FileTransportWriter(file, out, this); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java index df5d5b908..9b88d841e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportReader.java @@ -38,9 +38,6 @@ class FileTransportReader implements TransportConnectionReader { } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - if (recognised) { - file.delete(); - plugin.readerFinished(file); - } + plugin.readerFinished(file, exception, recognised); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java index 2752f7c8b..92eb26541 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/file/FileTransportWriter.java @@ -18,14 +18,11 @@ class FileTransportWriter implements TransportConnectionWriter { private final File file; private final OutputStream out; - private final long capacity; private final FilePlugin plugin; - FileTransportWriter(File file, OutputStream out, long capacity, - FilePlugin plugin) { + FileTransportWriter(File file, OutputStream out, FilePlugin plugin) { this.file = file; this.out = out; - this.capacity = capacity; this.plugin = plugin; } @@ -39,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter { return plugin.getMaxIdleTime(); } - @Override - public long getCapacity() { - return capacity; - } - @Override public OutputStream getOutputStream() { return out; @@ -56,7 +48,6 @@ class FileTransportWriter implements TransportConnectionWriter { } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - if (exception) file.delete(); - else plugin.writerFinished(file); + plugin.writerFinished(file, exception); } } 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 78b3cab7c..326a83774 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 @@ -207,20 +207,16 @@ abstract class TcpPlugin implements DuplexPlugin { } @Override - public void poll(Collection connected) { + public void poll(Map contacts) { if (!isRunning()) return; backoff.increment(); - Map remote = - callback.getRemoteProperties(); - for (Entry e : remote.entrySet()) { - ContactId c = e.getKey(); - if (!connected.contains(c)) connectAndCallBack(c, e.getValue()); + for (Entry e : contacts.entrySet()) { + connectAndCallBack(e.getKey(), e.getValue()); } } private void connectAndCallBack(ContactId c, TransportProperties p) { ioExecutor.execute(() -> { - if (!isRunning()) return; DuplexTransportConnection d = createConnection(p); if (d != null) { backoff.reset(); @@ -230,13 +226,8 @@ abstract class TcpPlugin implements DuplexPlugin { } @Override - public DuplexTransportConnection createConnection(ContactId c) { + public DuplexTransportConnection createConnection(TransportProperties p) { if (!isRunning()) return null; - return createConnection(callback.getRemoteProperties(c)); - } - - @Nullable - private DuplexTransportConnection createConnection(TransportProperties p) { for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { if (!isConnectable(remote)) { if (LOG.isLoggable(INFO)) { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java index 2af1852a0..0cd09917d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PluginManagerImplTest.java @@ -15,7 +15,6 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.Clock; -import org.briarproject.bramble.api.ui.UiCallback; import org.briarproject.bramble.test.BrambleTestCase; import org.jmock.Expectations; import org.jmock.Mockery; @@ -26,9 +25,7 @@ import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; import static org.briarproject.bramble.test.TestUtils.getTransportId; @@ -40,19 +37,20 @@ public class PluginManagerImplTest extends BrambleTestCase { setThreadingPolicy(new Synchroniser()); }}; Executor ioExecutor = Executors.newSingleThreadExecutor(); - ScheduledExecutorService scheduler = context.mock(ScheduledExecutorService.class); + ScheduledExecutorService scheduler = + context.mock(ScheduledExecutorService.class); SecureRandom random = new SecureRandom(); Clock clock = context.mock(Clock.class); EventBus eventBus = context.mock(EventBus.class); PluginConfig pluginConfig = context.mock(PluginConfig.class); ConnectionManager connectionManager = context.mock(ConnectionManager.class); - ConnectionRegistry connectionRegistry = context.mock(ConnectionRegistry.class); + ConnectionRegistry connectionRegistry = + context.mock(ConnectionRegistry.class); SettingsManager settingsManager = context.mock(SettingsManager.class); TransportPropertyManager transportPropertyManager = context.mock(TransportPropertyManager.class); - UiCallback uiCallback = context.mock(UiCallback.class); // Two simplex plugin factories: both create plugins, one fails to start SimplexPluginFactory simplexFactory = @@ -124,9 +122,9 @@ public class PluginManagerImplTest extends BrambleTestCase { oneOf(duplexPlugin).stop(); }}); - PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler, eventBus, - pluginConfig, connectionManager, connectionRegistry, settingsManager, - transportPropertyManager, random, clock, uiCallback); + PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler, + eventBus, pluginConfig, connectionManager, connectionRegistry, + settingsManager, transportPropertyManager, random, clock); // Two plugins should be started and stopped p.startService(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java index 5b0297595..3a60eb39d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerTest.java @@ -15,6 +15,8 @@ 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.simplex.SimplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.ImmediateExecutor; @@ -24,13 +26,15 @@ import org.jmock.lib.legacy.ClassImposteriser; import org.junit.Test; import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.briarproject.bramble.test.TestUtils.getTransportId; @@ -44,6 +48,8 @@ public class PollerTest extends BrambleMockTestCase { context.mock(ConnectionRegistry.class); private final PluginManager pluginManager = context.mock(PluginManager.class); + private final TransportPropertyManager transportPropertyManager = + context.mock(TransportPropertyManager.class); private final Clock clock = context.mock(Clock.class); private final ScheduledFuture future = context.mock(ScheduledFuture.class); private final SecureRandom random; @@ -51,6 +57,7 @@ public class PollerTest extends BrambleMockTestCase { private final Executor ioExecutor = new ImmediateExecutor(); private final TransportId transportId = getTransportId(); private final ContactId contactId = new ContactId(234); + private final TransportProperties properties = new TransportProperties(); private final int pollingInterval = 60 * 1000; private final long now = System.currentTimeMillis(); @@ -66,8 +73,8 @@ public class PollerTest extends BrambleMockTestCase { SimplexPlugin simplexPlugin1 = context.mock(SimplexPlugin.class, "simplexPlugin1"); TransportId simplexId1 = getTransportId(); - List simplexPlugins = Arrays.asList(simplexPlugin, - simplexPlugin1); + List simplexPlugins = + asList(simplexPlugin, simplexPlugin1); TransportConnectionWriter simplexWriter = context.mock(TransportConnectionWriter.class); @@ -76,8 +83,8 @@ public class PollerTest extends BrambleMockTestCase { TransportId duplexId = getTransportId(); DuplexPlugin duplexPlugin1 = context.mock(DuplexPlugin.class, "duplexPlugin1"); - List duplexPlugins = Arrays.asList(duplexPlugin, - duplexPlugin1); + List duplexPlugins = + asList(duplexPlugin, duplexPlugin1); DuplexTransportConnection duplexConnection = context.mock(DuplexTransportConnection.class); @@ -96,8 +103,12 @@ public class PollerTest extends BrambleMockTestCase { will(returnValue(simplexId1)); oneOf(connectionRegistry).isConnected(contactId, simplexId1); will(returnValue(false)); + // Get the transport properties + oneOf(transportPropertyManager).getRemoteProperties(contactId, + simplexId1); + will(returnValue(properties)); // Connect to the contact - oneOf(simplexPlugin1).createWriter(contactId); + oneOf(simplexPlugin1).createWriter(properties); will(returnValue(simplexWriter)); // Pass the connection to the connection manager oneOf(connectionManager).manageOutgoingConnection(contactId, @@ -105,7 +116,7 @@ public class PollerTest extends BrambleMockTestCase { // Get the duplex plugins oneOf(pluginManager).getDuplexPlugins(); will(returnValue(duplexPlugins)); - // The first plugin supports polling + // The duplex plugin supports polling oneOf(duplexPlugin).shouldPoll(); will(returnValue(true)); // Check whether the contact is already connected @@ -113,8 +124,12 @@ public class PollerTest extends BrambleMockTestCase { will(returnValue(duplexId)); oneOf(connectionRegistry).isConnected(contactId, duplexId); will(returnValue(false)); + // Get the transport properties + oneOf(transportPropertyManager).getRemoteProperties(contactId, + duplexId); + will(returnValue(properties)); // Connect to the contact - oneOf(duplexPlugin).createConnection(contactId); + oneOf(duplexPlugin).createConnection(properties); will(returnValue(duplexConnection)); // Pass the connection to the connection manager oneOf(connectionManager).manageOutgoingConnection(contactId, @@ -125,7 +140,8 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new ContactStatusChangedEvent(contactId, true)); } @@ -165,8 +181,12 @@ public class PollerTest extends BrambleMockTestCase { // Check whether the contact is already connected oneOf(connectionRegistry).isConnected(contactId, transportId); will(returnValue(false)); + // Get the transport properties + oneOf(transportPropertyManager).getRemoteProperties(contactId, + transportId); + will(returnValue(properties)); // Connect to the contact - oneOf(plugin).createConnection(contactId); + oneOf(plugin).createConnection(properties); will(returnValue(duplexConnection)); // Pass the connection to the connection manager oneOf(connectionManager).manageOutgoingConnection(contactId, @@ -174,15 +194,15 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new ConnectionClosedEvent(contactId, transportId, false)); } - @Test - public void testRescheduleOnConnectionOpened() throws Exception { + public void testRescheduleOnConnectionOpened() { Plugin plugin = context.mock(Plugin.class); context.checking(new Expectations() {{ @@ -205,14 +225,15 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId, false)); } @Test - public void testRescheduleDoesNotReplaceEarlierTask() throws Exception { + public void testRescheduleDoesNotReplaceEarlierTask() { Plugin plugin = context.mock(Plugin.class); context.checking(new Expectations() {{ @@ -248,7 +269,8 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId, false)); @@ -257,7 +279,7 @@ public class PollerTest extends BrambleMockTestCase { } @Test - public void testRescheduleReplacesLaterTask() throws Exception { + public void testRescheduleReplacesLaterTask() { Plugin plugin = context.mock(Plugin.class); context.checking(new Expectations() {{ @@ -296,7 +318,8 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId, false)); @@ -306,8 +329,7 @@ public class PollerTest extends BrambleMockTestCase { @Test public void testPollsOnTransportEnabled() throws Exception { - Plugin plugin = context.mock(Plugin.class); - List connected = Collections.singletonList(contactId); + DuplexPlugin plugin = context.mock(DuplexPlugin.class); context.checking(new Expectations() {{ allowing(plugin).getId(); @@ -335,20 +357,69 @@ public class PollerTest extends BrambleMockTestCase { oneOf(scheduler).schedule(with(any(Runnable.class)), with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); will(returnValue(future)); - // Poll the plugin + // Get the transport properties and connected contacts + oneOf(transportPropertyManager).getRemoteProperties(transportId); + will(returnValue(singletonMap(contactId, properties))); oneOf(connectionRegistry).getConnectedContacts(transportId); - will(returnValue(connected)); - oneOf(plugin).poll(connected); + will(returnValue(emptyList())); + // Poll the plugin + oneOf(plugin).poll(singletonMap(contactId, properties)); }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new TransportEnabledEvent(transportId)); } @Test - public void testCancelsPollingOnTransportDisabled() throws Exception { + public void testDoesNotPollIfAllContactsAreConnected() throws Exception { + DuplexPlugin plugin = context.mock(DuplexPlugin.class); + + context.checking(new Expectations() {{ + allowing(plugin).getId(); + will(returnValue(transportId)); + // Get the plugin + oneOf(pluginManager).getPlugin(transportId); + will(returnValue(plugin)); + // The plugin supports polling + oneOf(plugin).shouldPoll(); + will(returnValue(true)); + // Schedule a polling task immediately + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L), + with(MILLISECONDS)); + will(returnValue(future)); + will(new RunAction()); + // Running the polling task schedules the next polling task + oneOf(plugin).getPollingInterval(); + will(returnValue(pollingInterval)); + oneOf(random).nextDouble(); + will(returnValue(0.5)); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(scheduler).schedule(with(any(Runnable.class)), + with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); + will(returnValue(future)); + // Get the transport properties and connected contacts + oneOf(transportPropertyManager).getRemoteProperties(transportId); + will(returnValue(singletonMap(contactId, properties))); + oneOf(connectionRegistry).getConnectedContacts(transportId); + will(returnValue(singletonList(contactId))); + // All contacts are connected, so don't poll the plugin + }}); + + Poller p = new Poller(ioExecutor, scheduler, connectionManager, + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); + + p.eventOccurred(new TransportEnabledEvent(transportId)); + } + + @Test + public void testCancelsPollingOnTransportDisabled() { Plugin plugin = context.mock(Plugin.class); context.checking(new Expectations() {{ @@ -371,7 +442,8 @@ public class PollerTest extends BrambleMockTestCase { }}); Poller p = new Poller(ioExecutor, scheduler, connectionManager, - connectionRegistry, pluginManager, random, clock); + connectionRegistry, pluginManager, transportPropertyManager, + random, clock); p.eventOccurred(new TransportEnabledEvent(transportId)); p.eventOccurred(new TransportDisabledEvent(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 b5449ed34..8cc15b7f2 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 @@ -23,8 +23,6 @@ import java.net.ServerSocket; import java.net.Socket; import java.util.Collections; import java.util.Comparator; -import java.util.Hashtable; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -40,7 +38,6 @@ import static org.junit.Assert.assertTrue; public class LanTcpPluginTest extends BrambleTestCase { - private final ContactId contactId = new ContactId(234); private final Backoff backoff = new TestBackoff(); @Test @@ -160,12 +157,10 @@ public class LanTcpPluginTest extends BrambleTestCase { error.set(true); } }).start(); - // Tell the plugin about the port + // Connect to the port TransportProperties p = new TransportProperties(); p.put("ipPorts", addrString + ":" + port); - callback.remote.put(contactId, p); - // Connect to the port - DuplexTransportConnection d = plugin.createConnection(contactId); + DuplexTransportConnection d = plugin.createConnection(p); assertNotNull(d); // Check that the connection was accepted assertTrue(latch.await(5, SECONDS)); @@ -281,7 +276,7 @@ public class LanTcpPluginTest extends BrambleTestCase { } @Test - public void testComparatorPrefersNonZeroPorts() throws Exception { + public void testComparatorPrefersNonZeroPorts() { Comparator comparator = new LanAddressComparator(); InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234); InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0); @@ -294,7 +289,7 @@ public class LanTcpPluginTest extends BrambleTestCase { } @Test - public void testComparatorPrefersLongerPrefixes() throws Exception { + public void testComparatorPrefersLongerPrefixes() { Comparator comparator = new LanAddressComparator(); InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0); InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0); @@ -314,7 +309,7 @@ public class LanTcpPluginTest extends BrambleTestCase { } @Test - public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception { + public void testComparatorPrefersSiteLocalToLinkLocal() { Comparator comparator = new LanAddressComparator(); InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0); InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0); @@ -345,8 +340,6 @@ public class LanTcpPluginTest extends BrambleTestCase { @NotNullByDefault private static class Callback implements DuplexPluginCallback { - private final Map remote = - new Hashtable<>(); private final CountDownLatch propertiesLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final TransportProperties local = new TransportProperties(); @@ -361,16 +354,6 @@ public class LanTcpPluginTest extends BrambleTestCase { return local; } - @Override - public Map getRemoteProperties() { - return remote; - } - - @Override - public TransportProperties getRemoteProperties(ContactId c) { - return remote.get(c); - } - @Override public void mergeSettings(Settings s) { } @@ -381,20 +364,6 @@ public class LanTcpPluginTest extends BrambleTestCase { propertiesLatch.countDown(); } - @Override - public int showChoice(String[] options, String... message) { - return -1; - } - - @Override - public boolean showConfirmationMessage(String... message) { - return false; - } - - @Override - public void showMessage(String... message) { - } - @Override public void incomingConnectionCreated(DuplexTransportConnection d) { connectionsLatch.countDown(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java index b0463eab3..00372d4ee 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/CaptureArgumentAction.java @@ -20,7 +20,7 @@ public class CaptureArgumentAction implements Action { } @Override - public Object invoke(Invocation invocation) throws Throwable { + public Object invoke(Invocation invocation) { captured.set(capturedClass.cast(invocation.getParameter(index))); return null; } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java index 961783459..be9cd23d5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestPluginConfigModule.java @@ -9,13 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import java.util.Collection; -import java.util.Collections; import javax.annotation.Nullable; import dagger.Module; import dagger.Provides; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.briarproject.bramble.test.TestUtils.getTransportId; @Module @@ -51,12 +52,12 @@ public class TestPluginConfigModule { @Override public Collection getDuplexFactories() { - return Collections.emptyList(); + return emptyList(); } @Override public Collection getSimplexFactories() { - return Collections.singletonList(simplex); + return singletonList(simplex); } @Override diff --git a/bramble-j2se/libs/jnotify-0.94.jar b/bramble-j2se/libs/jnotify-0.94.jar deleted file mode 100644 index c4904349d7447018b3f06a9d1f632d90ac191773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30754 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