diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/nullsafety/NullSafety.java b/bramble-api/src/main/java/org/briarproject/bramble/api/nullsafety/NullSafety.java index 8e98561ee7f185d0c41b56f4c68b1d25645f5c29..5ff717a96b8a4f42e719aa080fbd121c2b6b9a81 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/nullsafety/NullSafety.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/nullsafety/NullSafety.java @@ -22,4 +22,11 @@ public class NullSafety { @Nullable Object b) { if ((a == null) == (b == null)) throw new AssertionError(); } + + /** + * Checks that the argument is null. + */ + public static void requireNull(@Nullable Object o) { + if (o != null) throw new AssertionError(); + } } 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 4ce802ced90b6fc1f87c15c9ce2098e09c858d7f..ce0e5787e7ee88f313a004b5884ab32ebce622e3 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 @@ -3,10 +3,11 @@ package org.briarproject.bramble.api.plugin.duplex; 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.ConnectionHandler; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; -import org.briarproject.bramble.api.rendezvous.RendezvousHandler; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import javax.annotation.Nullable; @@ -49,8 +50,12 @@ public interface DuplexPlugin extends Plugin { boolean supportsRendezvous(); /** - * Creates and returns a handler that uses the given key material to - * rendezvous with a pending contact. + * Creates and returns an endpoint that uses the given key material to + * rendezvous with a pending contact, and the given connection handler to + * handle incoming connections. Returns null if an endpoint cannot be + * created. */ - RendezvousHandler createRendezvousHandler(KeyMaterialSource k); + @Nullable + RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, + boolean alice, ConnectionHandler incoming); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousConstants.java deleted file mode 100644 index 00ac61fe176274b03d8cc3f84fa9875a19d07dbb..0000000000000000000000000000000000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousConstants.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.briarproject.bramble.api.rendezvous; - -public interface RendezvousConstants { - - /** - * Label for deriving key material from the master key. - */ - String KEY_MATERIAL_LABEL = - "org.briarproject.bramble.rendezvous/KEY_MATERIAL"; -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousCrypto.java b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousCrypto.java deleted file mode 100644 index e6cacef09bdfd31b2cff08a0171db26f7f6dbbde..0000000000000000000000000000000000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousCrypto.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.briarproject.bramble.api.rendezvous; - -import org.briarproject.bramble.api.crypto.SecretKey; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.TransportId; - -@NotNullByDefault -public interface RendezvousCrypto { - - KeyMaterialSource createKeyMaterialSource(SecretKey masterKey, - TransportId t); -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousHandler.java b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousEndpoint.java similarity index 85% rename from bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousHandler.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousEndpoint.java index a48e2d619bd80301d0a4e4d94bbb633ea9a7cc23..07fbb8f53362bae01a02203c4f71ea0c8973dde0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousHandler.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/RendezvousEndpoint.java @@ -2,13 +2,14 @@ package org.briarproject.bramble.api.rendezvous; import org.briarproject.bramble.api.properties.TransportProperties; +import java.io.Closeable; import java.io.IOException; /** * An interface for making and accepting rendezvous connections with a pending * contact over a given transport. */ -public interface RendezvousHandler { +public interface RendezvousEndpoint extends Closeable { /** * Returns a set of transport properties for connecting to the pending @@ -20,5 +21,6 @@ public interface RendezvousHandler { * Closes the handler and releases any resources held by it, such as * network sockets. */ + @Override void close() throws IOException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/event/RendezvousFailedEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/event/RendezvousFailedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..0530db1e258f97655791adf0e02571358b4bb711 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/rendezvous/event/RendezvousFailedEvent.java @@ -0,0 +1,25 @@ +package org.briarproject.bramble.api.rendezvous.event; + +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +/** + * An event that is broadcast when a rendezvous with a pending contact fails. + */ +@Immutable +@NotNullByDefault +public class RendezvousFailedEvent extends Event { + + private final PendingContactId pendingContactId; + + public RendezvousFailedEvent(PendingContactId pendingContactId) { + this.pendingContactId = pendingContactId; + } + + public PendingContactId getPendingContactId() { + return pendingContactId; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java index 6ca1c4ec89fecfeea2b874b28e9a92635f8d73ed..d0834d682c8cbd20c8fd4b89edcea2cd3259560e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreEagerSingletons.java @@ -7,6 +7,7 @@ import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.properties.PropertiesModule; +import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.transport.TransportModule; @@ -28,6 +29,8 @@ public interface BrambleCoreEagerSingletons { void inject(PropertiesModule.EagerSingletons init); + void inject(RendezvousModule.EagerSingletons init); + void inject(SystemModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init); @@ -42,6 +45,7 @@ public interface BrambleCoreEagerSingletons { inject(new DatabaseExecutorModule.EagerSingletons()); inject(new IdentityModule.EagerSingletons()); inject(new LifecycleModule.EagerSingletons()); + inject(new RendezvousModule.EagerSingletons()); inject(new PluginModule.EagerSingletons()); inject(new PropertiesModule.EagerSingletons()); inject(new SystemModule.EagerSingletons()); 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 04e54027b9fbf04c67cd917cd2ce96586132be66..0dec35bc5dd7709e9a430f297caf1568d97da488 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 @@ -23,7 +23,7 @@ import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent; import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; -import org.briarproject.bramble.api.rendezvous.RendezvousHandler; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; @@ -398,7 +398,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener { } @Override - public RendezvousHandler createRendezvousHandler(KeyMaterialSource k) { + public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, + boolean alice, ConnectionHandler incoming) { throw new UnsupportedOperationException(); } 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 5bc57948760c15586faa2bef1b1dabe935b0de89..4ca15d0b986644e216abaf05ed3712d089e58de1 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 @@ -13,7 +13,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; 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.RendezvousHandler; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.util.IoUtils; import java.io.IOException; @@ -309,7 +309,8 @@ abstract class TcpPlugin implements DuplexPlugin { } @Override - public RendezvousHandler createRendezvousHandler(KeyMaterialSource k) { + public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, + boolean alice, ConnectionHandler incoming) { throw new UnsupportedOperationException(); } 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 5cfcf5db49ab70e9ab4c7415ca314232a7deb4f0..76d8f6109a99bf7913cbde2809ef8cafad325862 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 @@ -26,7 +26,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; 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.RendezvousHandler; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.system.Clock; @@ -613,7 +613,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } @Override - public RendezvousHandler createRendezvousHandler(KeyMaterialSource k) { + public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, + boolean alice, ConnectionHandler incoming) { throw new UnsupportedOperationException(); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..6a8c7d836d4fdd11c9718c3973b2745b610c102c --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousConstants.java @@ -0,0 +1,34 @@ +package org.briarproject.bramble.rendezvous; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.MINUTES; + +interface RendezvousConstants { + + /** + * The current version of the rendezvous protocol. + */ + byte PROTOCOL_VERSION = 0; + + /** + * How long to try to rendezvous with a pending contact before giving up. + */ + long RENDEZVOUS_TIMEOUT_MS = DAYS.toMillis(2); + + /** + * How often to try to rendezvous with pending contacts. + */ + long POLLING_INTERVAL_MS = MINUTES.toMillis(1); + + /** + * Label for deriving the rendezvous key from the static master key. + */ + String RENDEZVOUS_KEY_LABEL = + "org.briarproject.bramble.rendezvous/RENDEZVOUS_KEY"; + + /** + * Label for deriving key material from the rendezvous key. + */ + String KEY_MATERIAL_LABEL = + "org.briarproject.bramble.rendezvous/KEY_MATERIAL"; +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCrypto.java new file mode 100644 index 0000000000000000000000000000000000000000..0fcd84417ac0e84205085b1e55c1597bbf97f72e --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCrypto.java @@ -0,0 +1,15 @@ +package org.briarproject.bramble.rendezvous; + +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; + +@NotNullByDefault +interface RendezvousCrypto { + + SecretKey deriveRendezvousKey(SecretKey staticMasterKey); + + KeyMaterialSource createKeyMaterialSource(SecretKey rendezvousKey, + TransportId t); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCryptoImpl.java index 01877ae4885d2d046e4f67d33f3815a3258e2fb9..c0cf89f944e8847291920445ce03fa1a91698055 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousCryptoImpl.java @@ -5,12 +5,13 @@ import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; -import org.briarproject.bramble.api.rendezvous.RendezvousCrypto; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.rendezvous.RendezvousConstants.KEY_MATERIAL_LABEL; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.KEY_MATERIAL_LABEL; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_KEY_LABEL; import static org.briarproject.bramble.util.StringUtils.toUtf8; @Immutable @@ -25,10 +26,16 @@ class RendezvousCryptoImpl implements RendezvousCrypto { } @Override - public KeyMaterialSource createKeyMaterialSource(SecretKey masterKey, + public SecretKey deriveRendezvousKey(SecretKey staticMasterKey) { + return crypto.deriveKey(RENDEZVOUS_KEY_LABEL, staticMasterKey, + new byte[] {PROTOCOL_VERSION}); + } + + @Override + public KeyMaterialSource createKeyMaterialSource(SecretKey rendezvousKey, TransportId t) { - SecretKey sourceKey = crypto.deriveKey(KEY_MATERIAL_LABEL, masterKey, - toUtf8(t.getString())); + SecretKey sourceKey = crypto.deriveKey(KEY_MATERIAL_LABEL, + rendezvousKey, toUtf8(t.getString())); return new KeyMaterialSourceImpl(sourceKey); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousModule.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousModule.java index f166303a5dd9a08034e3db94edb974f22379b845..41bf14f66a09b74cb40eb47591e2a0b03ab57b8a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousModule.java @@ -1,6 +1,10 @@ package org.briarproject.bramble.rendezvous; -import org.briarproject.bramble.api.rendezvous.RendezvousCrypto; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; + +import javax.inject.Inject; +import javax.inject.Singleton; import dagger.Module; import dagger.Provides; @@ -8,9 +12,23 @@ import dagger.Provides; @Module public class RendezvousModule { + public static class EagerSingletons { + @Inject + RendezvousPoller rendezvousPoller; + } + @Provides RendezvousCrypto provideRendezvousCrypto( RendezvousCryptoImpl rendezvousCrypto) { return rendezvousCrypto; } + + @Provides + @Singleton + RendezvousPoller provideRendezvousPoller(LifecycleManager lifecycleManager, + EventBus eventBus, RendezvousPollerImpl rendezvousPoller) { + lifecycleManager.registerService(rendezvousPoller); + eventBus.addListener(rendezvousPoller); + return rendezvousPoller; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPoller.java b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPoller.java new file mode 100644 index 0000000000000000000000000000000000000000..4ecfc32a6aaa7eb863ee97b029348c9266c1eb7f --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPoller.java @@ -0,0 +1,7 @@ +package org.briarproject.bramble.rendezvous; + +/** + * Empty interface for injecting the rendezvous poller. + */ +interface RendezvousPoller { +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..b7d355548a545fdeaa77d1b41f93969586b18330 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/rendezvous/RendezvousPollerImpl.java @@ -0,0 +1,365 @@ +package org.briarproject.bramble.rendezvous; + +import org.briarproject.bramble.PoliteExecutor; +import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent; +import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.crypto.TransportCrypto; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventExecutor; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.Service; +import org.briarproject.bramble.api.lifecycle.ServiceException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.ConnectionHandler; +import org.briarproject.bramble.api.plugin.ConnectionManager; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.plugin.TransportConnectionReader; +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.properties.TransportProperties; +import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; +import org.briarproject.bramble.api.rendezvous.event.RendezvousFailedEvent; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.Scheduler; + +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +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.nullsafety.NullSafety.requireNull; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS; +import static org.briarproject.bramble.util.IoUtils.tryToClose; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener { + + private static final Logger LOG = + getLogger(RendezvousPollerImpl.class.getName()); + + private final ScheduledExecutorService scheduler; + private final DatabaseComponent db; + private final IdentityManager identityManager; + private final TransportCrypto transportCrypto; + private final RendezvousCrypto rendezvousCrypto; + private final PluginManager pluginManager; + private final ConnectionManager connectionManager; + private final EventBus eventBus; + private final Clock clock; + + // Executor that runs one task at a time + private final Executor worker; + // The following fields are only accessed on the worker + private final Map<TransportId, PluginState> pluginStates = new HashMap<>(); + private final Map<PendingContactId, CryptoState> cryptoStates = + new HashMap<>(); + @Nullable + private KeyPair handshakeKeyPair = null; + + @Inject + RendezvousPollerImpl(@IoExecutor Executor ioExecutor, + @Scheduler ScheduledExecutorService scheduler, + DatabaseComponent db, + IdentityManager identityManager, + TransportCrypto transportCrypto, + RendezvousCrypto rendezvousCrypto, + PluginManager pluginManager, + ConnectionManager connectionManager, + EventBus eventBus, + Clock clock) { + this.scheduler = scheduler; + this.db = db; + this.identityManager = identityManager; + this.transportCrypto = transportCrypto; + this.rendezvousCrypto = rendezvousCrypto; + this.pluginManager = pluginManager; + this.connectionManager = connectionManager; + this.eventBus = eventBus; + this.clock = clock; + worker = new PoliteExecutor("RendezvousPoller", ioExecutor, 1); + } + + @Override + public void startService() throws ServiceException { + try { + db.transaction(true, txn -> { + Collection<PendingContact> pending = db.getPendingContacts(txn); + // Use a commit action to prevent races with add/remove events + txn.attach(() -> addPendingContactsAsync(pending)); + }); + } catch (DbException e) { + throw new ServiceException(e); + } + scheduler.scheduleAtFixedRate(this::poll, POLLING_INTERVAL_MS, + POLLING_INTERVAL_MS, MILLISECONDS); + } + + @EventExecutor + private void addPendingContactsAsync(Collection<PendingContact> pending) { + worker.execute(() -> { + for (PendingContact p : pending) addPendingContact(p); + }); + } + + // Worker + private void addPendingContact(PendingContact p) { + long now = clock.currentTimeMillis(); + long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS; + if (expiry > now) { + scheduler.schedule(() -> expirePendingContactAsync(p.getId()), + expiry - now, MILLISECONDS); + } else { + eventBus.broadcast(new RendezvousFailedEvent(p.getId())); + return; + } + try { + if (handshakeKeyPair == null) { + handshakeKeyPair = db.transactionWithResult(true, + identityManager::getHandshakeKeys); + } + SecretKey staticMasterKey = transportCrypto + .deriveStaticMasterKey(p.getPublicKey(), handshakeKeyPair); + SecretKey rendezvousKey = rendezvousCrypto + .deriveRendezvousKey(staticMasterKey); + boolean alice = transportCrypto + .isAlice(p.getPublicKey(), handshakeKeyPair); + CryptoState cs = new CryptoState(rendezvousKey, alice); + requireNull(cryptoStates.put(p.getId(), cs)); + for (PluginState ps : pluginStates.values()) { + RendezvousEndpoint endpoint = + createEndpoint(ps.plugin, p.getId(), cs); + if (endpoint != null) + requireNull(ps.endpoints.put(p.getId(), endpoint)); + } + } catch (DbException | GeneralSecurityException e) { + logException(LOG, WARNING, e); + } + } + + @Scheduler + private void expirePendingContactAsync(PendingContactId p) { + worker.execute(() -> expirePendingContact(p)); + } + + // Worker + private void expirePendingContact(PendingContactId p) { + if (removePendingContact(p)) + eventBus.broadcast(new RendezvousFailedEvent(p)); + } + + // Worker + private boolean removePendingContact(PendingContactId p) { + // We can come here twice if a pending contact fails and is then removed + if (cryptoStates.remove(p) == null) return false; + for (PluginState ps : pluginStates.values()) { + RendezvousEndpoint endpoint = ps.endpoints.remove(p); + if (endpoint != null) tryToClose(endpoint, LOG, INFO); + } + return true; + } + + @Nullable + private RendezvousEndpoint createEndpoint(DuplexPlugin plugin, + PendingContactId p, CryptoState cs) { + TransportId t = plugin.getId(); + KeyMaterialSource k = + rendezvousCrypto.createKeyMaterialSource(cs.rendezvousKey, t); + Handler h = new Handler(p, t, true); + return plugin.createRendezvousEndpoint(k, cs.alice, h); + } + + @Scheduler + private void poll() { + worker.execute(() -> { + for (PluginState ps : pluginStates.values()) poll(ps); + }); + } + + // Worker + private void poll(PluginState ps) { + List<Pair<TransportProperties, ConnectionHandler>> properties = + new ArrayList<>(); + for (Entry<PendingContactId, RendezvousEndpoint> e : + ps.endpoints.entrySet()) { + TransportProperties props = + e.getValue().getRemoteTransportProperties(); + Handler h = new Handler(e.getKey(), ps.plugin.getId(), false); + properties.add(new Pair<>(props, h)); + } + ps.plugin.poll(properties); + } + + @Override + public void stopService() { + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof PendingContactAddedEvent) { + PendingContactAddedEvent p = (PendingContactAddedEvent) e; + addPendingContactAsync(p.getPendingContact()); + } else if (e instanceof PendingContactRemovedEvent) { + PendingContactRemovedEvent p = (PendingContactRemovedEvent) e; + removePendingContactAsync(p.getId()); + } else if (e instanceof TransportEnabledEvent) { + TransportEnabledEvent t = (TransportEnabledEvent) e; + addTransportAsync(t.getTransportId()); + } else if (e instanceof TransportDisabledEvent) { + TransportDisabledEvent t = (TransportDisabledEvent) e; + removeTransportAsync(t.getTransportId()); + } + } + + @EventExecutor + private void addPendingContactAsync(PendingContact p) { + worker.execute(() -> { + addPendingContact(p); + poll(p.getId()); + }); + } + + // Worker + private void poll(PendingContactId p) { + for (PluginState ps : pluginStates.values()) { + RendezvousEndpoint endpoint = ps.endpoints.get(p); + if (endpoint != null) { + TransportProperties props = + endpoint.getRemoteTransportProperties(); + Handler h = new Handler(p, ps.plugin.getId(), false); + ps.plugin.poll(singletonList(new Pair<>(props, h))); + } + } + } + + @EventExecutor + private void removePendingContactAsync(PendingContactId p) { + worker.execute(() -> removePendingContact(p)); + } + + @EventExecutor + private void addTransportAsync(TransportId t) { + Plugin p = pluginManager.getPlugin(t); + if (p instanceof DuplexPlugin) { + DuplexPlugin d = (DuplexPlugin) p; + if (d.supportsRendezvous()) + worker.execute(() -> addTransport(d)); + } + } + + // Worker + private void addTransport(DuplexPlugin plugin) { + TransportId t = plugin.getId(); + Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>(); + for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) { + RendezvousEndpoint endpoint = + createEndpoint(plugin, e.getKey(), e.getValue()); + if (endpoint != null) endpoints.put(e.getKey(), endpoint); + } + requireNull(pluginStates.put(t, new PluginState(plugin, endpoints))); + } + + @EventExecutor + private void removeTransportAsync(TransportId t) { + worker.execute(() -> removeTransport(t)); + } + + // Worker + private void removeTransport(TransportId t) { + PluginState ps = pluginStates.remove(t); + if (ps != null) { + for (RendezvousEndpoint endpoint : ps.endpoints.values()) { + tryToClose(endpoint, LOG, INFO); + } + } + } + + private static class PluginState { + + private final DuplexPlugin plugin; + private final Map<PendingContactId, RendezvousEndpoint> endpoints; + + private PluginState(DuplexPlugin plugin, + Map<PendingContactId, RendezvousEndpoint> endpoints) { + this.plugin = plugin; + this.endpoints = endpoints; + } + } + + private static class CryptoState { + + private final SecretKey rendezvousKey; + private final boolean alice; + + private CryptoState(SecretKey rendezvousKey, boolean alice) { + this.rendezvousKey = rendezvousKey; + this.alice = alice; + } + } + + private class Handler implements ConnectionHandler { + + private final PendingContactId pendingContactId; + private final TransportId transportId; + private final boolean incoming; + + private Handler(PendingContactId pendingContactId, + TransportId transportId, boolean incoming) { + this.pendingContactId = pendingContactId; + this.transportId = transportId; + this.incoming = incoming; + } + + @Override + public void handleConnection(DuplexTransportConnection c) { + if (incoming) { + connectionManager.manageIncomingConnection(pendingContactId, + transportId, c); + } else { + connectionManager.manageOutgoingConnection(pendingContactId, + transportId, c); + } + } + + @Override + public void handleReader(TransportConnectionReader r) { + throw new UnsupportedOperationException(); + } + + @Override + public void handleWriter(TransportConnectionWriter w) { + throw new UnsupportedOperationException(); + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..92af0f780eda0db9d74b35890f61d2fda9d0f488 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/rendezvous/RendezvousPollerImplTest.java @@ -0,0 +1,359 @@ +package org.briarproject.bramble.rendezvous; + +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent; +import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.crypto.TransportCrypto; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.plugin.ConnectionHandler; +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.properties.TransportProperties; +import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; +import org.briarproject.bramble.api.rendezvous.event.RendezvousFailedEvent; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.CaptureArgumentAction; +import org.briarproject.bramble.test.DbExpectations; +import org.briarproject.bramble.test.ImmediateExecutor; +import org.jmock.Expectations; +import org.junit.Before; +import org.junit.Test; + +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; +import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS; +import static org.briarproject.bramble.test.CollectionMatcher.collectionOf; +import static org.briarproject.bramble.test.PairMatcher.pairOf; +import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; +import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; +import static org.briarproject.bramble.test.TestUtils.getPendingContact; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTransportId; +import static org.briarproject.bramble.test.TestUtils.getTransportProperties; + +public class RendezvousPollerImplTest extends BrambleMockTestCase { + + private final ScheduledExecutorService scheduler = + context.mock(ScheduledExecutorService.class); + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final IdentityManager identityManager = + context.mock(IdentityManager.class); + private final TransportCrypto transportCrypto = + context.mock(TransportCrypto.class); + private final RendezvousCrypto rendezvousCrypto = + context.mock(RendezvousCrypto.class); + private final PluginManager pluginManager = + context.mock(PluginManager.class); + private final ConnectionManager connectionManager = + context.mock(ConnectionManager.class); + private final EventBus eventBus = context.mock(EventBus.class); + private final Clock clock = context.mock(Clock.class); + private final DuplexPlugin plugin = context.mock(DuplexPlugin.class); + private final KeyMaterialSource keyMaterialSource = + context.mock(KeyMaterialSource.class); + private final RendezvousEndpoint rendezvousEndpoint = + context.mock(RendezvousEndpoint.class); + + private final Executor ioExecutor = new ImmediateExecutor(); + private final PendingContact pendingContact = getPendingContact(); + private final KeyPair handshakeKeyPair = + new KeyPair(getAgreementPublicKey(), getAgreementPrivateKey()); + private final SecretKey staticMasterKey = getSecretKey(); + private final SecretKey rendezvousKey = getSecretKey(); + private final TransportId transportId = getTransportId(); + private final TransportProperties transportProperties = + getTransportProperties(3); + private final boolean alice = new Random().nextBoolean(); + + private RendezvousPollerImpl rendezvousPoller; + + @Before + public void setUp() { + rendezvousPoller = new RendezvousPollerImpl(ioExecutor, scheduler, db, + identityManager, transportCrypto, rendezvousCrypto, + pluginManager, connectionManager, eventBus, clock); + } + + @Test + public void testAddsPendingContactsAndSchedulesExpiryAtStartup() + throws Exception { + Transaction txn = new Transaction(null, true); + long now = pendingContact.getTimestamp() + RENDEZVOUS_TIMEOUT_MS - 1000; + AtomicReference<Runnable> captureExpiryTask = new AtomicReference<>(); + + context.checking(new DbExpectations() {{ + // Load the pending contacts + oneOf(db).transaction(with(true), withDbRunnable(txn)); + oneOf(db).getPendingContacts(txn); + will(returnValue(singletonList(pendingContact))); + // Schedule the first poll + oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)), + with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS), + with(MILLISECONDS)); + // Calculate the pending contact's expiry time, 1 second from now + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + // Capture the expiry task, we'll run it later + oneOf(scheduler).schedule(with(any(Runnable.class)), with(1000L), + with(MILLISECONDS)); + will(new CaptureArgumentAction<>(captureExpiryTask, Runnable.class, + 0)); + // Load our handshake key pair + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + will(returnValue(handshakeKeyPair)); + // Derive the rendezvous key + oneOf(transportCrypto).deriveStaticMasterKey( + pendingContact.getPublicKey(), handshakeKeyPair); + will(returnValue(staticMasterKey)); + oneOf(rendezvousCrypto).deriveRendezvousKey(staticMasterKey); + will(returnValue(rendezvousKey)); + oneOf(transportCrypto).isAlice(pendingContact.getPublicKey(), + handshakeKeyPair); + will(returnValue(alice)); + }}); + + rendezvousPoller.startService(); + context.assertIsSatisfied(); + + context.checking(new Expectations() {{ + // Run the expiry task + oneOf(eventBus).broadcast(with(any(RendezvousFailedEvent.class))); + }}); + + captureExpiryTask.get().run(); + } + + @Test + public void testBroadcastsEventWhenExpiredPendingContactIsAdded() { + long now = pendingContact.getTimestamp() + RENDEZVOUS_TIMEOUT_MS; + + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(eventBus).broadcast(with(any(RendezvousFailedEvent.class))); + }}); + + rendezvousPoller.eventOccurred( + new PendingContactAddedEvent(pendingContact)); + } + + @Test + public void testCreatesAndClosesEndpointsWhenPendingContactIsAddedAndRemoved() + throws Exception { + Transaction txn = new Transaction(null, true); + long now = pendingContact.getTimestamp(); + + // Enable the transport - no endpoints should be created yet + context.checking(new Expectations() {{ + oneOf(pluginManager).getPlugin(transportId); + will(returnValue(plugin)); + oneOf(plugin).supportsRendezvous(); + will(returnValue(true)); + allowing(plugin).getId(); + will(returnValue(transportId)); + }}); + + rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + context.assertIsSatisfied(); + + // Add the pending contact - endpoint should be created and polled + context.checking(new DbExpectations() {{ + // Add pending contact + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(scheduler).schedule(with(any(Runnable.class)), + with(RENDEZVOUS_TIMEOUT_MS), with(MILLISECONDS)); + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + will(returnValue(handshakeKeyPair)); + oneOf(transportCrypto).deriveStaticMasterKey( + pendingContact.getPublicKey(), handshakeKeyPair); + will(returnValue(staticMasterKey)); + oneOf(rendezvousCrypto).deriveRendezvousKey(staticMasterKey); + will(returnValue(rendezvousKey)); + oneOf(transportCrypto).isAlice(pendingContact.getPublicKey(), + handshakeKeyPair); + will(returnValue(alice)); + oneOf(rendezvousCrypto).createKeyMaterialSource(rendezvousKey, + transportId); + will(returnValue(keyMaterialSource)); + oneOf(plugin).createRendezvousEndpoint(with(keyMaterialSource), + with(alice), with(any(ConnectionHandler.class))); + will(returnValue(rendezvousEndpoint)); + // Poll newly added pending contact + oneOf(rendezvousEndpoint).getRemoteTransportProperties(); + will(returnValue(transportProperties)); + oneOf(plugin).poll(with(collectionOf(pairOf( + equal(transportProperties), + any(ConnectionHandler.class))))); + }}); + + rendezvousPoller.eventOccurred( + new PendingContactAddedEvent(pendingContact)); + context.assertIsSatisfied(); + + // Remove the pending contact - endpoint should be closed + context.checking(new Expectations() {{ + oneOf(rendezvousEndpoint).close(); + }}); + + rendezvousPoller.eventOccurred( + new PendingContactRemovedEvent(pendingContact.getId())); + context.assertIsSatisfied(); + + // Disable the transport - endpoint is already closed + rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + } + + @Test + public void testCreatesAndClosesEndpointsWhenPendingContactIsAddedAndExpired() + throws Exception { + Transaction txn = new Transaction(null, true); + long now = pendingContact.getTimestamp(); + AtomicReference<Runnable> captureExpiryTask = new AtomicReference<>(); + + // Enable the transport - no endpoints should be created yet + context.checking(new Expectations() {{ + oneOf(pluginManager).getPlugin(transportId); + will(returnValue(plugin)); + oneOf(plugin).supportsRendezvous(); + will(returnValue(true)); + allowing(plugin).getId(); + will(returnValue(transportId)); + }}); + + rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + context.assertIsSatisfied(); + + // Add the pending contact - endpoint should be created and polled + context.checking(new DbExpectations() {{ + // Add pending contact + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + // Capture the expiry task, we'll run it later + oneOf(scheduler).schedule(with(any(Runnable.class)), + with(RENDEZVOUS_TIMEOUT_MS), with(MILLISECONDS)); + will(new CaptureArgumentAction<>(captureExpiryTask, Runnable.class, + 0)); + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + will(returnValue(handshakeKeyPair)); + oneOf(transportCrypto).deriveStaticMasterKey( + pendingContact.getPublicKey(), handshakeKeyPair); + will(returnValue(staticMasterKey)); + oneOf(rendezvousCrypto).deriveRendezvousKey(staticMasterKey); + will(returnValue(rendezvousKey)); + oneOf(transportCrypto).isAlice(pendingContact.getPublicKey(), + handshakeKeyPair); + will(returnValue(alice)); + oneOf(rendezvousCrypto).createKeyMaterialSource(rendezvousKey, + transportId); + will(returnValue(keyMaterialSource)); + oneOf(plugin).createRendezvousEndpoint(with(keyMaterialSource), + with(alice), with(any(ConnectionHandler.class))); + will(returnValue(rendezvousEndpoint)); + // Poll newly added pending contact + oneOf(rendezvousEndpoint).getRemoteTransportProperties(); + will(returnValue(transportProperties)); + oneOf(plugin).poll(with(collectionOf(pairOf( + equal(transportProperties), + any(ConnectionHandler.class))))); + }}); + + rendezvousPoller.eventOccurred( + new PendingContactAddedEvent(pendingContact)); + context.assertIsSatisfied(); + + // The pending contact expires - endpoint should be closed + context.checking(new Expectations() {{ + oneOf(rendezvousEndpoint).close(); + oneOf(eventBus).broadcast(with(any(RendezvousFailedEvent.class))); + }}); + + captureExpiryTask.get().run(); + context.assertIsSatisfied(); + + // Remove the pending contact - endpoint is already closed + rendezvousPoller.eventOccurred( + new PendingContactRemovedEvent(pendingContact.getId())); + context.assertIsSatisfied(); + + // Disable the transport - endpoint is already closed + rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + } + + @Test + public void testCreatesAndClosesEndpointsWhenTransportIsEnabledAndDisabled() + throws Exception { + Transaction txn = new Transaction(null, true); + long now = pendingContact.getTimestamp(); + + // Add the pending contact - no endpoints should be created yet + context.checking(new DbExpectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(scheduler).schedule(with(any(Runnable.class)), + with(RENDEZVOUS_TIMEOUT_MS), with(MILLISECONDS)); + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + will(returnValue(handshakeKeyPair)); + oneOf(transportCrypto).deriveStaticMasterKey( + pendingContact.getPublicKey(), handshakeKeyPair); + will(returnValue(staticMasterKey)); + oneOf(rendezvousCrypto).deriveRendezvousKey(staticMasterKey); + will(returnValue(rendezvousKey)); + oneOf(transportCrypto).isAlice(pendingContact.getPublicKey(), + handshakeKeyPair); + will(returnValue(alice)); + }}); + + rendezvousPoller.eventOccurred( + new PendingContactAddedEvent(pendingContact)); + context.assertIsSatisfied(); + + // Enable the transport - endpoint should be created + context.checking(new Expectations() {{ + oneOf(pluginManager).getPlugin(transportId); + will(returnValue(plugin)); + oneOf(plugin).supportsRendezvous(); + will(returnValue(true)); + allowing(plugin).getId(); + will(returnValue(transportId)); + oneOf(rendezvousCrypto).createKeyMaterialSource(rendezvousKey, + transportId); + will(returnValue(keyMaterialSource)); + oneOf(plugin).createRendezvousEndpoint(with(keyMaterialSource), + with(alice), with(any(ConnectionHandler.class))); + will(returnValue(rendezvousEndpoint)); + }}); + + rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId)); + context.assertIsSatisfied(); + + // Disable the transport - endpoint should be closed + context.checking(new Expectations() {{ + oneOf(rendezvousEndpoint).close(); + }}); + + rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId)); + context.assertIsSatisfied(); + + // Remove the pending contact - endpoint is already closed + rendezvousPoller.eventOccurred( + new PendingContactRemovedEvent(pendingContact.getId())); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/RunAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/RunAction.java index 0ec8d7ac8a61227ff1cb0c0db293f314ac4d144a..79fb712201f24f044067d3d1058c3cc8ed6d851b 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/RunAction.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/RunAction.java @@ -7,7 +7,7 @@ import org.jmock.api.Invocation; public class RunAction implements Action { @Override - public Object invoke(Invocation invocation) throws Throwable { + public Object invoke(Invocation invocation) { Runnable task = (Runnable) invocation.getParameter(0); task.run(); return null; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java index 7ee016201ec0a835110786d86bd533156a7d48ea..0e55a4be7721410f51abafd72a2709838c7c11a2 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionAction.java @@ -1,17 +1,18 @@ package org.briarproject.bramble.test; +import org.briarproject.bramble.api.db.CommitAction; import org.briarproject.bramble.api.db.DbRunnable; +import org.briarproject.bramble.api.db.TaskAction; import org.briarproject.bramble.api.db.Transaction; import org.hamcrest.Description; import org.jmock.api.Action; import org.jmock.api.Invocation; -public class RunTransactionAction implements Action { +class RunTransactionAction implements Action { private final Transaction txn; - @SuppressWarnings("WeakerAccess") - public RunTransactionAction(Transaction txn) { + RunTransactionAction(Transaction txn) { this.txn = txn; } @@ -19,6 +20,10 @@ public class RunTransactionAction implements Action { public Object invoke(Invocation invocation) throws Throwable { DbRunnable task = (DbRunnable) invocation.getParameter(1); task.run(txn); + for (CommitAction action : txn.getActions()) { + if (action instanceof TaskAction) + ((TaskAction) action).getTask().run(); + } return null; } @@ -26,5 +31,4 @@ public class RunTransactionAction implements Action { public void describeTo(Description description) { description.appendText("runs a task inside a database transaction"); } - } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithNullableResultAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithNullableResultAction.java index 9ed991c9266a8737a9293b9c5a07e8b45bfa8dba..1534f32361e7bd074f66ba1164db97d0fb0ad56d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithNullableResultAction.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithNullableResultAction.java @@ -1,16 +1,18 @@ package org.briarproject.bramble.test; +import org.briarproject.bramble.api.db.CommitAction; import org.briarproject.bramble.api.db.NullableDbCallable; +import org.briarproject.bramble.api.db.TaskAction; import org.briarproject.bramble.api.db.Transaction; import org.hamcrest.Description; import org.jmock.api.Action; import org.jmock.api.Invocation; -public class RunTransactionWithNullableResultAction implements Action { +class RunTransactionWithNullableResultAction implements Action { private final Transaction txn; - public RunTransactionWithNullableResultAction(Transaction txn) { + RunTransactionWithNullableResultAction(Transaction txn) { this.txn = txn; } @@ -18,7 +20,12 @@ public class RunTransactionWithNullableResultAction implements Action { public Object invoke(Invocation invocation) throws Throwable { NullableDbCallable task = (NullableDbCallable) invocation.getParameter(1); - return task.call(txn); + Object result = task.call(txn); + for (CommitAction action : txn.getActions()) { + if (action instanceof TaskAction) + ((TaskAction) action).getTask().run(); + } + return result; } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithResultAction.java b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithResultAction.java index cbd22b1260fc2d020b789c622db4c1ebfb3fa79e..d2089e97d9e404785b78c794cb729e7c1b3912ed 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithResultAction.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/RunTransactionWithResultAction.java @@ -1,23 +1,30 @@ package org.briarproject.bramble.test; +import org.briarproject.bramble.api.db.CommitAction; import org.briarproject.bramble.api.db.DbCallable; +import org.briarproject.bramble.api.db.TaskAction; import org.briarproject.bramble.api.db.Transaction; import org.hamcrest.Description; import org.jmock.api.Action; import org.jmock.api.Invocation; -public class RunTransactionWithResultAction implements Action { +class RunTransactionWithResultAction implements Action { private final Transaction txn; - public RunTransactionWithResultAction(Transaction txn) { + RunTransactionWithResultAction(Transaction txn) { this.txn = txn; } @Override public Object invoke(Invocation invocation) throws Throwable { DbCallable task = (DbCallable) invocation.getParameter(1); - return task.call(txn); + Object result = task.call(txn); + for (CommitAction action : txn.getActions()) { + if (action instanceof TaskAction) + ((TaskAction) action).getTask().run(); + } + return result; } @Override 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 980db3af9f59fe112f211f135b9ca09b43a89b79..16a0d0f31f0ab8bb3a251eafc16bb3d1567710b1 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 @@ -14,7 +14,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; 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.RendezvousHandler; +import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import java.io.IOException; import java.io.InputStream; @@ -192,7 +192,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { } @Override - public RendezvousHandler createRendezvousHandler(KeyMaterialSource k) { + public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, + boolean alice, ConnectionHandler incoming) { throw new UnsupportedOperationException(); }