Merge branch '1571-connection-manager-pending-contacts' into 'master'

Add rendezvous connection support to connection manager

Closes #1571

See merge request briar/briar!1120
This commit is contained in:
Torsten Grote
2019-06-04 14:08:05 +00:00
5 changed files with 279 additions and 55 deletions

View File

@@ -1,17 +1,46 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
public interface ConnectionManager { public interface ConnectionManager {
/**
* Manages an incoming connection from a contact over a simplex transport.
*/
void manageIncomingConnection(TransportId t, TransportConnectionReader r); void manageIncomingConnection(TransportId t, TransportConnectionReader r);
/**
* Manages an incoming connection from a contact over a duplex transport.
*/
void manageIncomingConnection(TransportId t, DuplexTransportConnection d); void manageIncomingConnection(TransportId t, DuplexTransportConnection d);
/**
* Manages an incoming handshake connection from a pending contact over a
* duplex transport.
*/
void manageIncomingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d);
/**
* Manages an outgoing connection to a contact over a simplex transport.
*/
void manageOutgoingConnection(ContactId c, TransportId t, void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w); TransportConnectionWriter w);
/**
* Manages an outgoing connection to a contact over a duplex transport.
*/
void manageOutgoingConnection(ContactId c, TransportId t, void manageOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d); DuplexTransportConnection d);
/**
* Manages an outgoing handshake connection to a pending contact over a
* duplex transport.
*/
void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d);
} }

View File

@@ -1,6 +1,11 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -44,6 +49,8 @@ class ConnectionManagerImpl implements ConnectionManager {
private final StreamReaderFactory streamReaderFactory; private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory; private final StreamWriterFactory streamWriterFactory;
private final SyncSessionFactory syncSessionFactory; private final SyncSessionFactory syncSessionFactory;
private final HandshakeManager handshakeManager;
private final ContactExchangeManager contactExchangeManager;
private final ConnectionRegistry connectionRegistry; private final ConnectionRegistry connectionRegistry;
@Inject @Inject
@@ -51,12 +58,16 @@ class ConnectionManagerImpl implements ConnectionManager {
KeyManager keyManager, StreamReaderFactory streamReaderFactory, KeyManager keyManager, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory, StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionRegistry connectionRegistry) { ConnectionRegistry connectionRegistry) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.keyManager = keyManager; this.keyManager = keyManager;
this.streamReaderFactory = streamReaderFactory; this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory; this.streamWriterFactory = streamWriterFactory;
this.syncSessionFactory = syncSessionFactory; this.syncSessionFactory = syncSessionFactory;
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionRegistry = connectionRegistry; this.connectionRegistry = connectionRegistry;
} }
@@ -72,6 +83,12 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new ManageIncomingDuplexConnection(t, d)); ioExecutor.execute(new ManageIncomingDuplexConnection(t, d));
} }
@Override
public void manageIncomingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new ManageIncomingHandshakeConnection(p, t, d));
}
@Override @Override
public void manageOutgoingConnection(ContactId c, TransportId t, public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w) { TransportConnectionWriter w) {
@@ -84,6 +101,12 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new ManageOutgoingDuplexConnection(c, t, d)); ioExecutor.execute(new ManageOutgoingDuplexConnection(c, t, d));
} }
@Override
public void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new ManageOutgoingHandshakeConnection(p, t, d));
}
private byte[] readTag(InputStream in) throws IOException { private byte[] readTag(InputStream in) throws IOException {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
read(in, tag); read(in, tag);
@@ -467,4 +490,205 @@ class ConnectionManagerImpl implements ConnectionManager {
disposeOnError(writer); disposeOnError(writer);
} }
} }
private class ManageIncomingHandshakeConnection implements Runnable {
private final PendingContactId pendingContactId;
private final TransportId transportId;
private final DuplexTransportConnection connection;
private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private ManageIncomingHandshakeConnection(
PendingContactId pendingContactId, TransportId transportId,
DuplexTransportConnection connection) {
this.pendingContactId = pendingContactId;
this.transportId = transportId;
this.connection = connection;
reader = connection.getReader();
writer = connection.getWriter();
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctxIn;
try {
byte[] tag = readTag(reader.getInputStream());
ctxIn = keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError(false);
return;
}
if (ctxIn == null) {
LOG.info("Unrecognised tag");
onError(false);
return;
}
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError(true);
return;
}
// Allocate the outgoing stream context
StreamContext ctxOut;
try {
ctxOut = keyManager.getStreamContext(pendingContactId,
transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
onError(true);
return;
}
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError(true);
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError(true);
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
// Flush the output stream to send the outgoing stream header
StreamWriter out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
HandshakeResult result = handshakeManager.handshake(
pendingContactId, in, out);
Contact contact = contactExchangeManager.exchangeContacts(
pendingContactId, connection, result.getMasterKey(),
result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
manageOutgoingConnection(contact.getId(), transportId,
connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError(true);
connectionRegistry.unregisterConnection(pendingContactId,
false);
}
}
private void onError(boolean recognised) {
disposeOnError(reader, recognised);
disposeOnError(writer);
}
}
private class ManageOutgoingHandshakeConnection implements Runnable {
private final PendingContactId pendingContactId;
private final TransportId transportId;
private final DuplexTransportConnection connection;
private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private ManageOutgoingHandshakeConnection(
PendingContactId pendingContactId, TransportId transportId,
DuplexTransportConnection connection) {
this.pendingContactId = pendingContactId;
this.transportId = transportId;
this.connection = connection;
reader = connection.getReader();
writer = connection.getWriter();
}
@Override
public void run() {
// Allocate the outgoing stream context
StreamContext ctxOut;
try {
ctxOut = keyManager.getStreamContext(pendingContactId,
transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
onError();
return;
}
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError();
return;
}
// Flush the output stream to send the outgoing stream header
StreamWriter out;
try {
out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
} catch (IOException e) {
logException(LOG, WARNING, e);
onError();
return;
}
// Read and recognise the tag
StreamContext ctxIn;
try {
byte[] tag = readTag(reader.getInputStream());
ctxIn = keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError();
return;
}
// Unrecognised tags are suspicious in this case
if (ctxIn == null) {
LOG.warning("Unrecognised tag for returning stream");
onError();
return;
}
// Check that the stream comes from the expected pending contact
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError();
return;
}
if (!inPendingContactId.equals(pendingContactId)) {
LOG.warning("Wrong pending contact ID for returning stream");
onError();
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError();
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
HandshakeResult result = handshakeManager.handshake(
pendingContactId, in, out);
Contact contact = contactExchangeManager.exchangeContacts(
pendingContactId, connection, result.getMasterKey(),
result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
manageOutgoingConnection(contact.getId(), transportId,
connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError();
connectionRegistry.unregisterConnection(pendingContactId,
false);
}
}
private void onError() {
// 'Recognised' is always true for outgoing connections
disposeOnError(reader, true);
disposeOnError(writer);
}
}
} }

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
@@ -14,19 +14,14 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestDuplexTransportConnection; import org.briarproject.bramble.test.TestDuplexTransportConnection;
import org.briarproject.bramble.test.TestStreamWriter;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.File; import java.io.File;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNotNull;
@@ -34,12 +29,12 @@ import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.fail; import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair; import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ContactExchangeIntegrationTest extends BrambleTestCase { public class ContactExchangeIntegrationTest extends BrambleTestCase {
@@ -116,8 +111,8 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
fail(); fail();
} }
}); });
aliceFinished.await(TIMEOUT, MILLISECONDS); assertTrue(aliceFinished.await(TIMEOUT, MILLISECONDS));
bobFinished.await(TIMEOUT, MILLISECONDS); assertTrue(bobFinished.await(TIMEOUT, MILLISECONDS));
assertContacts(verified, false); assertContacts(verified, false);
assertNoPendingContacts(); assertNoPendingContacts();
} }
@@ -155,8 +150,8 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
fail(); fail();
} }
}); });
aliceFinished.await(TIMEOUT, MILLISECONDS); assertTrue(aliceFinished.await(TIMEOUT, MILLISECONDS));
bobFinished.await(TIMEOUT, MILLISECONDS); assertTrue(bobFinished.await(TIMEOUT, MILLISECONDS));
assertContacts(verified, true); assertContacts(verified, true);
assertNoPendingContacts(); assertNoPendingContacts();
} }
@@ -168,54 +163,25 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
PendingContact aliceFromBob = addPendingContact(bob, alice); PendingContact aliceFromBob = addPendingContact(bob, alice);
assertPendingContacts(); assertPendingContacts();
PipedInputStream aliceHandshakeIn = new PipedInputStream();
PipedInputStream bobHandshakeIn = new PipedInputStream();
OutputStream aliceHandshakeOut = new PipedOutputStream(bobHandshakeIn);
OutputStream bobHandshakeOut = new PipedOutputStream(aliceHandshakeIn);
AtomicReference<HandshakeResult> aliceResult = new AtomicReference<>();
AtomicReference<HandshakeResult> bobResult = new AtomicReference<>();
TestDuplexTransportConnection[] pair = createPair(); TestDuplexTransportConnection[] pair = createPair();
TestDuplexTransportConnection aliceConnection = pair[0]; TestDuplexTransportConnection aliceConnection = pair[0];
TestDuplexTransportConnection bobConnection = pair[1]; TestDuplexTransportConnection bobConnection = pair[1];
CountDownLatch aliceFinished = new CountDownLatch(1); CountDownLatch aliceFinished = new CountDownLatch(1);
CountDownLatch bobFinished = new CountDownLatch(1); CountDownLatch bobFinished = new CountDownLatch(1);
boolean verified = random.nextBoolean();
alice.getIoExecutor().execute(() -> { alice.getEventBus().addListener(e -> {
try { if (e instanceof ContactAddedEvent) aliceFinished.countDown();
HandshakeResult result = alice.getHandshakeManager().handshake(
bobFromAlice.getId(), aliceHandshakeIn,
new TestStreamWriter(aliceHandshakeOut));
aliceResult.set(result);
alice.getContactExchangeManager().exchangeContacts(
bobFromAlice.getId(), aliceConnection,
result.getMasterKey(), result.isAlice(), verified);
aliceFinished.countDown();
} catch (Exception e) {
fail();
}
}); });
bob.getIoExecutor().execute(() -> { alice.getConnectionManager().manageOutgoingConnection(
try { bobFromAlice.getId(), DUPLEX_TRANSPORT_ID, aliceConnection);
HandshakeResult result = bob.getHandshakeManager().handshake( bob.getEventBus().addListener(e -> {
aliceFromBob.getId(), bobHandshakeIn, if (e instanceof ContactAddedEvent) bobFinished.countDown();
new TestStreamWriter(bobHandshakeOut));
bobResult.set(result);
bob.getContactExchangeManager().exchangeContacts(
aliceFromBob.getId(), bobConnection,
result.getMasterKey(), result.isAlice(), verified);
bobFinished.countDown();
} catch (Exception e) {
fail();
}
}); });
aliceFinished.await(TIMEOUT, MILLISECONDS); bob.getConnectionManager().manageIncomingConnection(
bobFinished.await(TIMEOUT, MILLISECONDS); aliceFromBob.getId(), DUPLEX_TRANSPORT_ID, bobConnection);
assertArrayEquals(aliceResult.get().getMasterKey().getBytes(), assertTrue(aliceFinished.await(TIMEOUT, MILLISECONDS));
bobResult.get().getMasterKey().getBytes()); assertTrue(bobFinished.await(TIMEOUT, MILLISECONDS));
assertNotEquals(aliceResult.get().isAlice(), bobResult.get().isAlice()); assertContacts(false, true);
assertContacts(verified, true);
assertNoPendingContacts(); assertNoPendingContacts();
} }

View File

@@ -4,10 +4,11 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -24,11 +25,13 @@ import dagger.Component;
interface ContactExchangeIntegrationTestComponent interface ContactExchangeIntegrationTestComponent
extends BrambleCoreEagerSingletons { extends BrambleCoreEagerSingletons {
ConnectionManager getConnectionManager();
ContactExchangeManager getContactExchangeManager(); ContactExchangeManager getContactExchangeManager();
ContactManager getContactManager(); ContactManager getContactManager();
HandshakeManager getHandshakeManager(); EventBus getEventBus();
IdentityManager getIdentityManager(); IdentityManager getIdentityManager();

View File

@@ -21,6 +21,7 @@ public class TestDuplexTransportConnection
private final TransportConnectionReader reader; private final TransportConnectionReader reader;
private final TransportConnectionWriter writer; private final TransportConnectionWriter writer;
@SuppressWarnings("WeakerAccess")
public TestDuplexTransportConnection(InputStream in, OutputStream out) { public TestDuplexTransportConnection(InputStream in, OutputStream out) {
reader = new TestTransportConnectionReader(in); reader = new TestTransportConnectionReader(in);
writer = new TestTransportConnectionWriter(out); writer = new TestTransportConnectionWriter(out);
@@ -42,8 +43,9 @@ public class TestDuplexTransportConnection
*/ */
public static TestDuplexTransportConnection[] createPair() public static TestDuplexTransportConnection[] createPair()
throws IOException { throws IOException {
PipedInputStream aliceIn = new PipedInputStream(); // Use 64k buffers to prevent deadlock
PipedInputStream bobIn = new PipedInputStream(); PipedInputStream aliceIn = new PipedInputStream(1 << 16);
PipedInputStream bobIn = new PipedInputStream(1 << 16);
PipedOutputStream aliceOut = new PipedOutputStream(bobIn); PipedOutputStream aliceOut = new PipedOutputStream(bobIn);
PipedOutputStream bobOut = new PipedOutputStream(aliceIn); PipedOutputStream bobOut = new PipedOutputStream(aliceIn);
TestDuplexTransportConnection alice = TestDuplexTransportConnection alice =