Merge branch '1232-pending-contact-states' into 'master'

Add ContactManager support for pending contact states

See merge request briar/briar!1122
This commit is contained in:
Torsten Grote
2019-06-06 21:31:43 +00:00
4 changed files with 304 additions and 21 deletions

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
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;
@@ -15,24 +16,34 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo; import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousFailedEvent;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
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.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
@@ -43,24 +54,29 @@ import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactManagerImpl implements ContactManager { class ContactManagerImpl implements ContactManager, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final KeyManager keyManager; private final KeyManager keyManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final PendingContactFactory pendingContactFactory; private final PendingContactFactory pendingContactFactory;
private final EventBus eventBus;
private final List<ContactHook> hooks; private final List<ContactHook> hooks = new CopyOnWriteArrayList<>();
private final ConcurrentMap<PendingContactId, PendingContactState> states =
new ConcurrentHashMap<>();
@Inject @Inject
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager, ContactManagerImpl(DatabaseComponent db,
KeyManager keyManager,
IdentityManager identityManager, IdentityManager identityManager,
PendingContactFactory pendingContactFactory) { PendingContactFactory pendingContactFactory,
EventBus eventBus) {
this.db = db; this.db = db;
this.keyManager = keyManager; this.keyManager = keyManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.pendingContactFactory = pendingContactFactory; this.pendingContactFactory = pendingContactFactory;
hooks = new CopyOnWriteArrayList<>(); this.eventBus = eventBus;
} }
@Override @Override
@@ -86,6 +102,7 @@ class ContactManagerImpl implements ContactManager {
throws DbException, GeneralSecurityException { throws DbException, GeneralSecurityException {
PendingContact pendingContact = db.getPendingContact(txn, p); PendingContact pendingContact = db.getPendingContact(txn, p);
db.removePendingContact(txn, p); db.removePendingContact(txn, p);
states.remove(p);
PublicKey theirPublicKey = pendingContact.getPublicKey(); PublicKey theirPublicKey = pendingContact.getPublicKey();
ContactId c = ContactId c =
db.addContact(txn, remote, local, theirPublicKey, verified); db.addContact(txn, remote, local, theirPublicKey, verified);
@@ -139,6 +156,7 @@ class ContactManagerImpl implements ContactManager {
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
setState(p.getId(), WAITING_FOR_CONNECTION);
return p; return p;
} }
@@ -156,7 +174,9 @@ class ContactManagerImpl implements ContactManager {
List<Pair<PendingContact, PendingContactState>> pairs = List<Pair<PendingContact, PendingContactState>> pairs =
new ArrayList<>(pendingContacts.size()); new ArrayList<>(pendingContacts.size());
for (PendingContact p : pendingContacts) { for (PendingContact p : pendingContacts) {
pairs.add(new Pair<>(p, WAITING_FOR_CONNECTION)); // TODO PendingContactState state = states.get(p.getId());
if (state == null) state = WAITING_FOR_CONNECTION;
pairs.add(new Pair<>(p, state));
} }
return pairs; return pairs;
} }
@@ -164,6 +184,7 @@ class ContactManagerImpl implements ContactManager {
@Override @Override
public void removePendingContact(PendingContactId p) throws DbException { public void removePendingContact(PendingContactId p) throws DbException {
db.transaction(false, txn -> db.removePendingContact(txn, p)); db.transaction(false, txn -> db.removePendingContact(txn, p));
states.remove(p);
} }
@Override @Override
@@ -263,4 +284,48 @@ class ContactManagerImpl implements ContactManager {
else return new AuthorInfo(UNVERIFIED, c.getAlias()); else return new AuthorInfo(UNVERIFIED, c.getAlias());
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof RendezvousConnectionOpenedEvent) {
RendezvousConnectionOpenedEvent r =
(RendezvousConnectionOpenedEvent) e;
setStateConnected(r.getPendingContactId());
} else if (e instanceof RendezvousConnectionClosedEvent) {
RendezvousConnectionClosedEvent r =
(RendezvousConnectionClosedEvent) e;
// We're only interested in failures - if the rendezvous succeeds
// the pending contact will be removed
if (!r.isSuccess()) setStateDisconnected(r.getPendingContactId());
} else if (e instanceof RendezvousFailedEvent) {
RendezvousFailedEvent r = (RendezvousFailedEvent) e;
setState(r.getPendingContactId(), FAILED);
}
}
/**
* Sets the state of the given pending contact and broadcasts an event.
*/
private void setState(PendingContactId p, PendingContactState state) {
states.put(p, state);
eventBus.broadcast(new PendingContactStateChangedEvent(p, state));
}
private void setStateConnected(PendingContactId p) {
// Set the state to ADDING_CONTACT if there's no current state or the
// current state is WAITING_FOR_CONNECTION
if (states.putIfAbsent(p, ADDING_CONTACT) == null ||
states.replace(p, WAITING_FOR_CONNECTION, ADDING_CONTACT)) {
eventBus.broadcast(new PendingContactStateChangedEvent(p,
ADDING_CONTACT));
}
}
private void setStateDisconnected(PendingContactId p) {
// Set the state to WAITING_FOR_CONNECTION if the current state is
// ADDING_CONTACT
if (states.replace(p, ADDING_CONTACT, WAITING_FOR_CONNECTION)) {
eventBus.broadcast(new PendingContactStateChangedEvent(p,
WAITING_FOR_CONNECTION));
}
}
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.contact;
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.contact.HandshakeManager;
import org.briarproject.bramble.api.event.EventBus;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -20,7 +21,9 @@ public class ContactModule {
@Provides @Provides
@Singleton @Singleton
ContactManager provideContactManager(ContactManagerImpl contactManager) { ContactManager provideContactManager(EventBus eventBus,
ContactManagerImpl contactManager) {
eventBus.addListener(contactManager);
return contactManager; return contactManager;
} }

View File

@@ -1,25 +1,32 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
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.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
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.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo; import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousFailedEvent;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import java.util.Collection;
@@ -28,6 +35,9 @@ import java.util.Random;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
@@ -38,6 +48,7 @@ import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String; import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
@@ -54,24 +65,31 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
context.mock(IdentityManager.class); context.mock(IdentityManager.class);
private final PendingContactFactory pendingContactFactory = private final PendingContactFactory pendingContactFactory =
context.mock(PendingContactFactory.class); context.mock(PendingContactFactory.class);
private final ContactManager contactManager; private final EventBus eventBus = context.mock(EventBus.class);
private final Author remote = getAuthor(); private final Author remote = getAuthor();
private final LocalAuthor localAuthor = getLocalAuthor(); private final LocalAuthor localAuthor = getLocalAuthor();
private final AuthorId local = localAuthor.getId(); private final AuthorId local = localAuthor.getId();
private final boolean verified = false, active = true; private final boolean verified = false, active = true;
private final Contact contact = getContact(remote, local, verified); private final Contact contact = getContact(remote, local, verified);
private final ContactId contactId = contact.getId(); private final ContactId contactId = contact.getId();
private final KeyPair handshakeKeyPair =
new KeyPair(getAgreementPublicKey(), getAgreementPrivateKey());
private final PendingContact pendingContact = getPendingContact();
private final SecretKey rootKey = getSecretKey();
private final long timestamp = System.currentTimeMillis();
private final boolean alice = new Random().nextBoolean();
public ContactManagerImplTest() { private ContactManagerImpl contactManager;
@Before
public void setUp() {
contactManager = new ContactManagerImpl(db, keyManager, contactManager = new ContactManagerImpl(db, keyManager,
identityManager, pendingContactFactory); identityManager, pendingContactFactory, eventBus);
} }
@Test @Test
public void testAddContact() throws Exception { public void testAddContact() throws Exception {
SecretKey rootKey = getSecretKey();
long timestamp = System.currentTimeMillis();
boolean alice = new Random().nextBoolean();
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
@@ -91,6 +109,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetContact() throws Exception { public void testGetContact() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
@@ -104,6 +123,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
public void testGetContactByAuthor() throws Exception { public void testGetContactByAuthor() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = singletonList(contact); Collection<Contact> contacts = singletonList(contact);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
@@ -116,6 +136,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test(expected = NoSuchContactException.class) @Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownAuthor() throws Exception { public void testGetContactByUnknownAuthor() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
@@ -129,6 +150,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
public void testGetContactByUnknownLocalAuthor() throws Exception { public void testGetContactByUnknownLocalAuthor() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = singletonList(contact); Collection<Contact> contacts = singletonList(contact);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
@@ -142,6 +164,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
public void testGetContacts() throws Exception { public void testGetContacts() throws Exception {
Collection<Contact> contacts = singletonList(contact); Collection<Contact> contacts = singletonList(contact);
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
@@ -154,6 +177,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testRemoveContact() throws Exception { public void testRemoveContact() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
@@ -187,6 +211,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testContactExists() throws Exception { public void testContactExists() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).containsContact(txn, remote.getId(), local); oneOf(db).containsContact(txn, remote.getId(), local);
@@ -206,6 +231,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).getContactsByAuthorId(txn, remote.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
}}); }});
AuthorInfo authorInfo = AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId()); contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus()); assertEquals(UNVERIFIED, authorInfo.getStatus());
@@ -223,6 +249,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).getContactsByAuthorId(txn, remote.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(emptyList())); will(returnValue(emptyList()));
}}); }});
AuthorInfo authorInfo = AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId()); contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNKNOWN, authorInfo.getStatus()); assertEquals(UNKNOWN, authorInfo.getStatus());
@@ -247,6 +274,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
will(returnValue(localAuthor)); will(returnValue(localAuthor));
never(db).getContactsByAuthorId(txn, remote.getId()); never(db).getContactsByAuthorId(txn, remote.getId());
}}); }});
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId()); authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
assertEquals(OURSELVES, authorInfo.getStatus()); assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias()); assertNull(authorInfo.getAlias());
@@ -265,19 +293,178 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetHandshakeLink() throws Exception { public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
PublicKey publicKey = getAgreementPublicKey();
PrivateKey privateKey = getAgreementPrivateKey();
KeyPair keyPair = new KeyPair(publicKey, privateKey);
String link = "briar://" + getRandomBase32String(BASE32_LINK_BYTES); String link = "briar://" + getRandomBase32String(BASE32_LINK_BYTES);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getHandshakeKeys(txn); oneOf(identityManager).getHandshakeKeys(txn);
will(returnValue(keyPair)); will(returnValue(handshakeKeyPair));
oneOf(pendingContactFactory).createHandshakeLink(publicKey); oneOf(pendingContactFactory).createHandshakeLink(
handshakeKeyPair.getPublic());
will(returnValue(link)); will(returnValue(link));
}}); }});
assertEquals(link, contactManager.getHandshakeLink()); assertEquals(link, contactManager.getHandshakeLink());
} }
@Test
public void testDefaultPendingContactState() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getPendingContacts(txn);
will(returnValue(singletonList(pendingContact)));
}});
// No events have happened for this pending contact, so the state
// should be WAITING_FOR_CONNECTION
Collection<Pair<PendingContact, PendingContactState>> pairs =
contactManager.getPendingContacts();
assertEquals(1, pairs.size());
Pair<PendingContact, PendingContactState> pair =
pairs.iterator().next();
assertEquals(pendingContact, pair.getFirst());
assertEquals(WAITING_FOR_CONNECTION, pair.getSecond());
}
@Test
public void testPendingContactExpiresBeforeConnection() {
// The pending contact expires - the FAILED state is broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == FAILED)));
}});
contactManager.eventOccurred(new RendezvousFailedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// A rendezvous connection is opened - no state is broadcast
contactManager.eventOccurred(new RendezvousConnectionOpenedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The rendezvous connection fails - no state is broadcast
contactManager.eventOccurred(new RendezvousConnectionClosedEvent(
pendingContact.getId(), false));
}
@Test
public void testPendingContactExpiresDuringFailedConnection() {
// A rendezvous connection is opened - the ADDING_CONTACT state is
// broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == ADDING_CONTACT)));
}});
contactManager.eventOccurred(new RendezvousConnectionOpenedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The pending contact expires - the FAILED state is broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == FAILED)));
}});
contactManager.eventOccurred(new RendezvousFailedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The rendezvous connection fails - no state is broadcast
contactManager.eventOccurred(new RendezvousConnectionClosedEvent(
pendingContact.getId(), false));
}
@Test
public void testPendingContactExpiresDuringSuccessfulConnection()
throws Exception {
Transaction txn = new Transaction(null, false);
// A rendezvous connection is opened - the ADDING_CONTACT state is
// broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == ADDING_CONTACT)));
}});
contactManager.eventOccurred(new RendezvousConnectionOpenedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The pending contact expires - the FAILED state is broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == FAILED)));
}});
contactManager.eventOccurred(new RendezvousFailedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The pending contact is converted to a contact - no state is broadcast
context.checking(new DbExpectations() {{
oneOf(db).getPendingContact(txn, pendingContact.getId());
will(returnValue(pendingContact));
oneOf(db).removePendingContact(txn, pendingContact.getId());
oneOf(db).addContact(txn, remote, local,
pendingContact.getPublicKey(), verified);
will(returnValue(contactId));
oneOf(db).setContactAlias(txn, contactId,
pendingContact.getAlias());
oneOf(identityManager).getHandshakeKeys(txn);
will(returnValue(handshakeKeyPair));
oneOf(keyManager).addContact(txn, contactId,
pendingContact.getPublicKey(), handshakeKeyPair);
oneOf(keyManager).addRotationKeys(txn, contactId, rootKey,
timestamp, alice, active);
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
}});
contactManager.addContact(txn, pendingContact.getId(), remote,
local, rootKey, timestamp, alice, verified, active);
context.assertIsSatisfied();
// The rendezvous connection succeeds - no state is broadcast
contactManager.eventOccurred(new RendezvousConnectionClosedEvent(
pendingContact.getId(), true));
}
@Test
public void testPendingContactRemovedDuringFailedConnection()
throws Exception {
Transaction txn = new Transaction(null, false);
// A rendezvous connection is opened - the ADDING_CONTACT state is
// broadcast
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == ADDING_CONTACT)));
}});
contactManager.eventOccurred(new RendezvousConnectionOpenedEvent(
pendingContact.getId()));
context.assertIsSatisfied();
// The pending contact is removed - no state is broadcast
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removePendingContact(txn, pendingContact.getId());
}});
contactManager.removePendingContact(pendingContact.getId());
context.assertIsSatisfied();
// The rendezvous connection fails - no state is broadcast
contactManager.eventOccurred(new RendezvousConnectionClosedEvent(
pendingContact.getId(), false));
}
} }

View File

@@ -0,0 +1,28 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.Predicate;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
public class PredicateMatcher<T> extends BaseMatcher<T> {
private final Class<T> matchedClass;
private final Predicate<T> predicate;
public PredicateMatcher(Class<T> matchedClass, Predicate<T> predicate) {
this.matchedClass = matchedClass;
this.predicate = predicate;
}
@Override
public boolean matches(Object item) {
if (matchedClass.isInstance(item))
return predicate.test(matchedClass.cast(item));
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("matches an item against a predicate");
}
}