Avoid DB lookups where possible.

This commit is contained in:
akwizgran
2011-11-24 22:09:04 +00:00
parent ff8010a945
commit 9345b5c71b
4 changed files with 510 additions and 144 deletions

View File

@@ -1,17 +1,27 @@
package net.sf.briar.api.db.event; package net.sf.briar.api.db.event;
import java.util.Collection;
import net.sf.briar.api.ContactId; import net.sf.briar.api.ContactId;
import net.sf.briar.api.protocol.Transport;
/** An event that is broadcast when a contact's transports are updated. */ /** An event that is broadcast when a contact's transports are updated. */
public class RemoteTransportsUpdatedEvent extends DatabaseEvent { public class RemoteTransportsUpdatedEvent extends DatabaseEvent {
private final ContactId contactId; private final ContactId contactId;
private final Collection<Transport> transports;
public RemoteTransportsUpdatedEvent(ContactId contactId) { public RemoteTransportsUpdatedEvent(ContactId contactId,
Collection<Transport> transports) {
this.contactId = contactId; this.contactId = contactId;
this.transports = transports;
} }
public ContactId getContactId() { public ContactId getContactId() {
return contactId; return contactId;
} }
public Collection<Transport> getTransports() {
return transports;
}
} }

View File

@@ -1226,7 +1226,7 @@ DatabaseCleaner.Callback {
contactLock.readLock().unlock(); contactLock.readLock().unlock();
} }
// Call the listeners outside the lock // Call the listeners outside the lock
callListeners(new RemoteTransportsUpdatedEvent(c)); callListeners(new RemoteTransportsUpdatedEvent(c, t.getTransports()));
} }
public void removeContact(ContactId c) throws DbException { public void removeContact(ContactId c) throws DbException {

View File

@@ -51,6 +51,7 @@ DatabaseListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor executor; private final Executor executor;
private final Cipher ivCipher; // Locking: this private final Cipher ivCipher; // Locking: this
private final Set<TransportId> localTransportIds; // Locking: this
private final Map<Bytes, Context> expected; // Locking: this private final Map<Bytes, Context> expected; // Locking: this
private boolean initialised = false; // Locking: this private boolean initialised = false; // Locking: this
@@ -62,9 +63,15 @@ DatabaseListener {
this.db = db; this.db = db;
this.executor = executor; this.executor = executor;
ivCipher = crypto.getIvCipher(); ivCipher = crypto.getIvCipher();
localTransportIds = new HashSet<TransportId>();
expected = new HashMap<Bytes, Context>(); expected = new HashMap<Bytes, Context>();
} }
// Package access for testing
synchronized boolean isInitialised() {
return initialised;
}
// Locking: this // Locking: this
private void initialise() throws DbException { private void initialise() throws DbException {
assert !initialised; assert !initialised;
@@ -76,7 +83,7 @@ DatabaseListener {
try { try {
for(TransportId t : transports) { for(TransportId t : transports) {
TransportIndex i = db.getRemoteIndex(c, t); TransportIndex i = db.getRemoteIndex(c, t);
if(i == null) continue; if(i == null) continue; // Contact doesn't support transport
ConnectionWindow w = db.getConnectionWindow(c, i); ConnectionWindow w = db.getConnectionWindow(c, i);
for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) { for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) {
Context ctx = new Context(c, t, i, e.getKey()); Context ctx = new Context(c, t, i, e.getKey());
@@ -89,6 +96,7 @@ DatabaseListener {
continue; continue;
} }
} }
localTransportIds.addAll(transports);
expected.putAll(ivs); expected.putAll(ivs);
initialised = true; initialised = true;
} }
@@ -100,7 +108,8 @@ DatabaseListener {
ErasableKey ivKey = crypto.deriveIvKey(secret, true); ErasableKey ivKey = crypto.deriveIvKey(secret, true);
try { try {
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
return new Bytes(ivCipher.doFinal(iv)); byte[] encryptedIv = ivCipher.doFinal(iv);
return new Bytes(encryptedIv);
} catch(BadPaddingException badCipher) { } catch(BadPaddingException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} catch(IllegalBlockSizeException badCipher) { } catch(IllegalBlockSizeException badCipher) {
@@ -127,8 +136,9 @@ DatabaseListener {
}); });
} }
private ConnectionContext acceptConnection(TransportId t, // Package access for testing
byte[] encryptedIv) throws DbException { ConnectionContext acceptConnection(TransportId t, byte[] encryptedIv)
throws DbException {
if(encryptedIv.length != IV_LENGTH) if(encryptedIv.length != IV_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
synchronized(this) { synchronized(this) {
@@ -189,11 +199,12 @@ DatabaseListener {
}); });
} else if(e instanceof RemoteTransportsUpdatedEvent) { } else if(e instanceof RemoteTransportsUpdatedEvent) {
// Update the expected IVs for the contact // Update the expected IVs for the contact
final ContactId c = RemoteTransportsUpdatedEvent r = (RemoteTransportsUpdatedEvent) e;
((RemoteTransportsUpdatedEvent) e).getContactId(); final ContactId c = r.getContactId();
final Collection<Transport> transports = r.getTransports();
executor.execute(new Runnable() { executor.execute(new Runnable() {
public void run() { public void run() {
updateContact(c); updateContact(c, transports);
} }
}); });
} }
@@ -212,7 +223,7 @@ DatabaseListener {
for(ContactId c : db.getContacts()) { for(ContactId c : db.getContacts()) {
try { try {
TransportIndex i = db.getRemoteIndex(c, t); TransportIndex i = db.getRemoteIndex(c, t);
if(i == null) continue; if(i == null) continue; // Contact doesn't support transport
ConnectionWindow w = db.getConnectionWindow(c, i); ConnectionWindow w = db.getConnectionWindow(c, i);
for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) { for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) {
Context ctx = new Context(c, t, i, e.getKey()); Context ctx = new Context(c, t, i, e.getKey());
@@ -228,33 +239,26 @@ DatabaseListener {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
return; return;
} }
localTransportIds.add(t);
expected.putAll(ivs); expected.putAll(ivs);
} }
private synchronized void updateContact(ContactId c) { private synchronized void updateContact(ContactId c,
Collection<Transport> transports) {
if(!initialised) return; if(!initialised) return;
// Don't recalculate IVs for transports that are already known // The ID <-> index mappings may have changed, so recalculate everything
Set<TransportIndex> known = new HashSet<TransportIndex>();
for(Context ctx : expected.values()) {
if(ctx.contactId.equals(c)) known.add(ctx.transportIndex);
}
Set<TransportIndex> current = new HashSet<TransportIndex>();
Map<Bytes, Context> ivs = new HashMap<Bytes, Context>(); Map<Bytes, Context> ivs = new HashMap<Bytes, Context>();
try { try {
for(Transport transport : db.getLocalTransports()) { for(Transport transport: transports) {
TransportId t = transport.getId(); TransportId t = transport.getId();
TransportIndex i = db.getRemoteIndex(c, t); if(!localTransportIds.contains(t)) continue;
if(i == null) continue; TransportIndex i = transport.getIndex();
current.add(i); ConnectionWindow w = db.getConnectionWindow(c, i);
// If the transport is not already known, calculate the IVs for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) {
if(!known.contains(i)) { Context ctx = new Context(c, t, i, e.getKey());
ConnectionWindow w = db.getConnectionWindow(c, i); ivs.put(calculateIv(ctx, e.getValue()), ctx);
for(Entry<Long, byte[]> e : w.getUnseen().entrySet()) {
Context ctx = new Context(c, t, i, e.getKey());
ivs.put(calculateIv(ctx, e.getValue()), ctx);
}
w.erase();
} }
w.erase();
} }
} catch(NoSuchContactException e) { } catch(NoSuchContactException e) {
// The contact was removed - clean up in removeContact() // The contact was removed - clean up in removeContact()
@@ -263,14 +267,10 @@ DatabaseListener {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
return; return;
} }
// Remove any IVs that are no longer current // Remove the old IVs
Iterator<Context> it = expected.values().iterator(); Iterator<Context> it = expected.values().iterator();
while(it.hasNext()) { while(it.hasNext()) if(it.next().contactId.equals(c)) it.remove();
Context ctx = it.next(); // Store the new IVs
if(ctx.contactId.equals(c) && !current.contains(ctx.transportIndex))
it.remove();
}
// Add any IVs that were not previously known
expected.putAll(ivs); expected.putAll(ivs);
} }

View File

@@ -2,6 +2,7 @@ package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH; import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@@ -16,13 +17,13 @@ import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
import net.sf.briar.api.db.event.TransportAddedEvent;
import net.sf.briar.api.protocol.Transport; import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportIndex; import net.sf.briar.api.protocol.TransportIndex;
import net.sf.briar.api.transport.ConnectionContext; import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.ConnectionRecogniser.Callback;
import net.sf.briar.api.transport.ConnectionWindow; import net.sf.briar.api.transport.ConnectionWindow;
import net.sf.briar.crypto.CryptoModule; import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.plugins.ImmediateExecutor; import net.sf.briar.plugins.ImmediateExecutor;
@@ -41,8 +42,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
private final byte[] inSecret; private final byte[] inSecret;
private final TransportId transportId; private final TransportId transportId;
private final TransportIndex localIndex, remoteIndex; private final TransportIndex localIndex, remoteIndex;
private final Collection<Transport> transports; private final Collection<Transport> localTransports, remoteTransports;
private final ConnectionWindow connectionWindow;
public ConnectionRecogniserImplTest() { public ConnectionRecogniserImplTest() {
super(); super();
@@ -54,51 +54,486 @@ public class ConnectionRecogniserImplTest extends TestCase {
transportId = new TransportId(TestUtils.getRandomId()); transportId = new TransportId(TestUtils.getRandomId());
localIndex = new TransportIndex(13); localIndex = new TransportIndex(13);
remoteIndex = new TransportIndex(7); remoteIndex = new TransportIndex(7);
Transport transport = new Transport(transportId, localIndex, Map<String, String> properties = Collections.singletonMap("foo", "bar");
Collections.singletonMap("foo", "bar")); Transport localTransport = new Transport(transportId, localIndex,
transports = Collections.singletonList(transport); properties);
connectionWindow = new ConnectionWindowImpl(crypto, remoteIndex, localTransports = Collections.singletonList(localTransport);
inSecret); Transport remoteTransport = new Transport(transportId, remoteIndex,
properties);
remoteTransports = Collections.singletonList(remoteTransport);
} }
@Test @Test
public void testUnexpectedIv() throws Exception { public void testUnexpectedIv() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery(); Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class); final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
// Initialise // Initialise
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports(); oneOf(db).getLocalTransports();
will(returnValue(transports)); will(returnValue(localTransports));
oneOf(db).getContacts(); oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId))); will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId); oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex)); will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex); oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(connectionWindow)); will(returnValue(window));
}}); }});
Executor executor = new ImmediateExecutor(); Executor executor = new ImmediateExecutor();
ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor); executor);
c.acceptConnection(transportId, new byte[IV_LENGTH], new Callback() { assertNull(c.acceptConnection(transportId, new byte[IV_LENGTH]));
public void connectionAccepted(ConnectionContext ctx) {
fail();
}
public void connectionRejected() {
// Expected
}
public void handleException(DbException e) {
fail();
}
});
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testExpectedIv() throws Exception { public void testExpectedIv() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// The IV should not be expected by the wrong transport
TransportId wrong = new TransportId(TestUtils.getRandomId());
assertNull(c.acceptConnection(wrong, encryptedIv));
// The IV should be expected by the right transport
ConnectionContext ctx = c.acceptConnection(transportId, encryptedIv);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
@Test
public void testContactRemovedAfterInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise before removing contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Ensure the recogniser is initialised
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, new byte[IV_LENGTH]));
assertTrue(c.isInitialised());
// Remove the contact
c.eventOccurred(new ContactRemovedEvent(contactId));
// The IV should not be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
context.assertIsSatisfied();
}
@Test
public void testContactRemovedBeforeInit() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise after removing contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.emptyList()));
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Remove the contact
c.eventOccurred(new ContactRemovedEvent(contactId));
// The IV should not be expected
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, encryptedIv));
assertTrue(c.isInitialised());
context.assertIsSatisfied();
}
@Test
public void testLocalTransportAddedAfterInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise before adding transport
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(Collections.emptyList()));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
// Add the transport
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// The IV should not be expected
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, encryptedIv));
assertTrue(c.isInitialised());
// Add the transport
c.eventOccurred(new TransportAddedEvent(transportId));
// The IV should be expected
ConnectionContext ctx = c.acceptConnection(transportId, encryptedIv);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
@Test
public void testLocalTransportAddedBeforeInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise after adding transport
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Add the transport
c.eventOccurred(new TransportAddedEvent(transportId));
// The IV should be expected
assertFalse(c.isInitialised());
ConnectionContext ctx = c.acceptConnection(transportId, encryptedIv);
assertTrue(c.isInitialised());
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
@Test
public void testRemoteTransportAddedAfterInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise before updating the contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(null));
// Update the contact
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// The IV should not be expected
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, encryptedIv));
assertTrue(c.isInitialised());
// Update the contact
c.eventOccurred(new RemoteTransportsUpdatedEvent(contactId,
remoteTransports));
// The IV should be expected
ConnectionContext ctx = c.acceptConnection(transportId, encryptedIv);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
@Test
public void testRemoteTransportAddedBeforeInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise after updating the contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Update the contact
c.eventOccurred(new RemoteTransportsUpdatedEvent(contactId,
remoteTransports));
// The IV should be expected
assertFalse(c.isInitialised());
ConnectionContext ctx = c.acceptConnection(transportId, encryptedIv);
assertTrue(c.isInitialised());
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
@Test
public void testRemoteTransportRemovedAfterInit() throws Exception {
final ConnectionWindow window = createConnectionWindow(remoteIndex);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise before updating the contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Ensure the recogniser is initialised
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, new byte[IV_LENGTH]));
assertTrue(c.isInitialised());
// Update the contact
c.eventOccurred(new RemoteTransportsUpdatedEvent(contactId,
Collections.<Transport>emptyList()));
// The IV should not be expected
assertNull(c.acceptConnection(transportId, encryptedIv));
context.assertIsSatisfied();
}
@Test
public void testRemoteTransportRemovedBeforeInit() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise after updating the contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(null));
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Update the contact
c.eventOccurred(new RemoteTransportsUpdatedEvent(contactId,
Collections.<Transport>emptyList()));
// The IV should not be expected
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, encryptedIv));
assertTrue(c.isInitialised());
context.assertIsSatisfied();
}
@Test
public void testRemoteTransportIndexChangedAfterInit() throws Exception {
// The contact changes the transport ID <-> index relationships
final TransportId transportId1 =
new TransportId(TestUtils.getRandomId());
final TransportIndex remoteIndex1 = new TransportIndex(11);
Map<String, String> properties = Collections.singletonMap("foo", "bar");
Transport remoteTransport = new Transport(transportId, remoteIndex1,
properties);
Transport remoteTransport1 = new Transport(transportId1, remoteIndex,
properties);
Collection<Transport> remoteTransports1 = Arrays.asList(
new Transport[] {remoteTransport, remoteTransport1});
// Use two local transports for this test
TransportIndex localIndex1 = new TransportIndex(17);
Transport localTransport = new Transport(transportId, localIndex,
properties);
Transport localTransport1 = new Transport(transportId1, localIndex1,
properties);
final Collection<Transport> localTransports1 = Arrays.asList(
new Transport[] {localTransport, localTransport1});
final ConnectionWindow window = createConnectionWindow(remoteIndex);
final ConnectionWindow window1 = createConnectionWindow(remoteIndex1);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Initialise before updating the contact
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
oneOf(db).getLocalTransports();
will(returnValue(localTransports1));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
// First, transportId <-> remoteIndex, transportId1 <-> remoteIndex
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).getRemoteIndex(contactId, transportId1);
will(returnValue(remoteIndex1));
oneOf(db).getConnectionWindow(contactId, remoteIndex1);
will(returnValue(window1));
// Later, transportId <-> remoteIndex1, transportId1 <-> remoteIndex
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).getConnectionWindow(contactId, remoteIndex1);
will(returnValue(window1));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(window));
oneOf(db).setConnectionWindow(contactId, remoteIndex, window);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniserImpl c = new ConnectionRecogniserImpl(crypto, db,
executor);
byte[] encryptedIv = calculateIv();
// Ensure the recogniser is initialised
assertFalse(c.isInitialised());
assertNull(c.acceptConnection(transportId, new byte[IV_LENGTH]));
assertTrue(c.isInitialised());
// Update the contact
c.eventOccurred(new RemoteTransportsUpdatedEvent(contactId,
remoteTransports1));
// The IV should not be expected by the old transport
assertNull(c.acceptConnection(transportId, encryptedIv));
// The IV should be expected by the new transport
ConnectionContext ctx = c.acceptConnection(transportId1, encryptedIv);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3, ctx.getConnectionNumber());
// The IV should no longer be expected
assertNull(c.acceptConnection(transportId1, encryptedIv));
// The window should have advanced
Map<Long, byte[]> unseen = window.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
}
private ConnectionWindow createConnectionWindow(TransportIndex index) {
return new ConnectionWindowImpl(crypto, index, inSecret) {
@Override
public void erase() {}
};
}
private byte[] calculateIv() throws Exception {
// Calculate the shared secret for connection number 3 // Calculate the shared secret for connection number 3
byte[] secret = inSecret; byte[] secret = inSecret;
for(int i = 0; i < 4; i++) { for(int i = 0; i < 4; i++) {
@@ -109,85 +544,6 @@ public class ConnectionRecogniserImplTest extends TestCase {
Cipher ivCipher = crypto.getIvCipher(); Cipher ivCipher = crypto.getIvCipher();
ivCipher.init(Cipher.ENCRYPT_MODE, ivKey); ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
byte[] iv = IvEncoder.encodeIv(true, remoteIndex.getInt(), 3); byte[] iv = IvEncoder.encodeIv(true, remoteIndex.getInt(), 3);
byte[] encryptedIv = ivCipher.doFinal(iv); return ivCipher.doFinal(iv);
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
// Initialise
oneOf(db).getLocalTransports();
will(returnValue(transports));
oneOf(db).getContacts();
will(returnValue(Collections.singletonList(contactId)));
oneOf(db).getRemoteIndex(contactId, transportId);
will(returnValue(remoteIndex));
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(connectionWindow));
// Update the window
oneOf(db).getConnectionWindow(contactId, remoteIndex);
will(returnValue(connectionWindow));
oneOf(db).setConnectionWindow(contactId, remoteIndex,
connectionWindow);
}});
Executor executor = new ImmediateExecutor();
ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db,
executor);
// The IV should not be expected by the wrong transport
TransportId wrong = new TransportId(TestUtils.getRandomId());
c.acceptConnection(wrong, encryptedIv, new Callback() {
public void connectionAccepted(ConnectionContext ctx) {
fail();
}
public void connectionRejected() {
// Expected
}
public void handleException(DbException e) {
fail();
}
});
// The IV should be expected by the right transport
c.acceptConnection(transportId, encryptedIv, new Callback() {
public void connectionAccepted(ConnectionContext ctx) {
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(remoteIndex, ctx.getTransportIndex());
assertEquals(3L, ctx.getConnectionNumber());
}
public void connectionRejected() {
fail();
}
public void handleException(DbException e) {
fail();
}
});
// The IV should no longer be expected
c.acceptConnection(transportId, encryptedIv, new Callback() {
public void connectionAccepted(ConnectionContext ctx) {
fail();
}
public void connectionRejected() {
// Expected
}
public void handleException(DbException e) {
fail();
}
});
// The window should have advanced
Map<Long, byte[]> unseen = connectionWindow.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) {
assertEquals(i != 3, unseen.containsKey(Long.valueOf(i)));
}
context.assertIsSatisfied();
} }
} }