diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java index 18c5da1c691ef626deb5634194cde763a1730b1f..f02b9b635cc3a40cd655074f1e3c8e8ff1bdb447 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java @@ -45,22 +45,4 @@ public interface IntroductionManager { Collection<IntroductionMessage> getIntroductionMessages(ContactId contactId) throws DbException; - /** Marks an introduction message as read or unread. */ - void setReadFlag(MessageId m, boolean read) throws DbException; - - - /** Get the session state for the given session ID */ - BdfDictionary getSessionState(Transaction txn, GroupId groupId, - byte[] sessionId) throws DbException, FormatException; - - /** Gets the group used for introductions with Contact c */ - Group getIntroductionGroup(Contact c); - - /** Get the local group used to store session states */ - Group getLocalGroup(); - - /** Send an introduction message */ - void sendMessage(Transaction txn, BdfDictionary message) - throws DbException, FormatException; - } diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java index 64af33b44d59cd2f8ff89d194cefb9fb7e97ae7d..b25f299e024870824fbbcd2f8329ae530b1dd085 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java @@ -24,11 +24,8 @@ import org.briarproject.api.event.IntroductionSucceededEvent; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.AuthorId; -import org.briarproject.api.introduction.IntroductionManager; -import org.briarproject.api.introduction.SessionId; import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportPropertyManager; -import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -40,6 +37,8 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; +import javax.inject.Inject; + import static java.util.logging.Level.WARNING; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; @@ -76,33 +75,36 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPO class IntroduceeManager { - private static final Logger LOG = Logger.getLogger(IntroduceeManager.class.getName()); + private final MessageSender messageSender; private final DatabaseComponent db; - private final IntroductionManager introductionManager; private final ClientHelper clientHelper; private final Clock clock; private final CryptoComponent cryptoComponent; private final TransportPropertyManager transportPropertyManager; private final AuthorFactory authorFactory; private final ContactManager contactManager; + private final IntroductionGroupFactory introductionGroupFactory; - IntroduceeManager(DatabaseComponent db, - IntroductionManager introductionManager, ClientHelper clientHelper, - Clock clock, CryptoComponent cryptoComponent, + @Inject + IntroduceeManager(MessageSender messageSender, DatabaseComponent db, + ClientHelper clientHelper, Clock clock, + CryptoComponent cryptoComponent, TransportPropertyManager transportPropertyManager, - AuthorFactory authorFactory, ContactManager contactManager) { + AuthorFactory authorFactory, ContactManager contactManager, + IntroductionGroupFactory introductionGroupFactory) { + this.messageSender = messageSender; this.db = db; - this.introductionManager = introductionManager; this.clientHelper = clientHelper; this.clock = clock; this.cryptoComponent = cryptoComponent; this.transportPropertyManager = transportPropertyManager; this.authorFactory = authorFactory; this.contactManager = contactManager; + this.introductionGroupFactory = introductionGroupFactory; } public BdfDictionary initialize(Transaction txn, GroupId groupId, @@ -113,9 +115,9 @@ class IntroduceeManager { Bytes salt = new Bytes(new byte[64]); cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); - Message localMsg = clientHelper - .createMessage(introductionManager.getLocalGroup().getId(), now, - BdfList.of(salt)); + Message localMsg = clientHelper.createMessage( + introductionGroupFactory.createLocalGroup().getId(), now, + BdfList.of(salt)); MessageId storageId = localMsg.getId(); // find out who is introducing us @@ -133,7 +135,7 @@ class IntroduceeManager { d.put(INTRODUCER, introducer.getAuthor().getName()); d.put(CONTACT_ID_1, introducer.getId().getInt()); d.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes()); - d.put(NOT_OUR_RESPONSE, new byte[0]); + d.put(NOT_OUR_RESPONSE, storageId); d.put(ANSWERED, false); // check if the contact we are introduced to does already exist @@ -147,7 +149,7 @@ class IntroduceeManager { // save local state to database clientHelper.addLocalMessage(txn, localMsg, - introductionManager.getClientId(), d, false); + IntroductionManagerImpl.CLIENT_ID, d, false); return d; } @@ -159,16 +161,10 @@ class IntroduceeManager { processStateUpdate(txn, engine.onMessageReceived(state, message)); } - public void acceptIntroduction(Transaction txn, final ContactId contactId, - final SessionId sessionId, final long timestamp) + public void acceptIntroduction(Transaction txn, BdfDictionary state, + final long timestamp) throws DbException, FormatException { - Contact c = db.getContact(txn, contactId); - Group g = introductionManager.getIntroductionGroup(c); - - BdfDictionary state = introductionManager - .getSessionState(txn, g.getId(), sessionId.getBytes()); - // get data to connect and derive a shared secret later long now = clock.currentTimeMillis(); KeyPair keyPair = cryptoComponent.generateAgreementKeyPair(); @@ -195,16 +191,10 @@ class IntroduceeManager { processStateUpdate(txn, engine.onLocalAction(state, localAction)); } - public void declineIntroduction(Transaction txn, final ContactId contactId, - final SessionId sessionId, final long timestamp) + public void declineIntroduction(Transaction txn, BdfDictionary state, + final long timestamp) throws DbException, FormatException { - Contact c = db.getContact(txn, contactId); - Group g = introductionManager.getIntroductionGroup(c); - - BdfDictionary state = introductionManager - .getSessionState(txn, g.getId(), sessionId.getBytes()); - // update session state state.put(ACCEPT, false); @@ -233,7 +223,7 @@ class IntroduceeManager { // send messages for (BdfDictionary d : result.toSend) { - introductionManager.sendMessage(txn, d); + messageSender.sendMessage(txn, d); } // broadcast events diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java index 93481367df01a89508d541d4ca6be4b21072288d..891c4b7b599e3eb2bf679a8eeadd49c237c8d98a 100644 --- a/briar-core/src/org/briarproject/introduction/IntroducerManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java @@ -10,7 +10,6 @@ import org.briarproject.api.data.BdfList; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; -import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -20,6 +19,8 @@ import org.briarproject.util.StringUtils; import java.io.IOException; import java.util.logging.Logger; +import javax.inject.Inject; + import static java.util.logging.Level.WARNING; import static org.briarproject.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS; import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_1; @@ -39,7 +40,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRO import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; import static org.briarproject.api.introduction.IntroductionConstants.STATE; import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; -import static org.briarproject.api.introduction.IntroductionConstants.TIME; import static org.briarproject.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; @@ -49,19 +49,22 @@ class IntroducerManager { private static final Logger LOG = Logger.getLogger(IntroducerManager.class.getName()); - private final IntroductionManager introductionManager; + private final MessageSender messageSender; private final ClientHelper clientHelper; private final Clock clock; private final CryptoComponent cryptoComponent; + private final IntroductionGroupFactory introductionGroupFactory; - IntroducerManager(IntroductionManager introductionManager, - ClientHelper clientHelper, Clock clock, - CryptoComponent cryptoComponent) { + @Inject + IntroducerManager(MessageSender messageSender, ClientHelper clientHelper, + Clock clock, CryptoComponent cryptoComponent, + IntroductionGroupFactory introductionGroupFactory) { - this.introductionManager = introductionManager; + this.messageSender = messageSender; this.clientHelper = clientHelper; this.clock = clock; this.cryptoComponent = cryptoComponent; + this.introductionGroupFactory = introductionGroupFactory; } public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2) @@ -72,13 +75,13 @@ class IntroducerManager { Bytes salt = new Bytes(new byte[64]); cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); - Message m = clientHelper - .createMessage(introductionManager.getLocalGroup().getId(), now, - BdfList.of(salt)); + Message m = clientHelper.createMessage( + introductionGroupFactory.createLocalGroup().getId(), now, + BdfList.of(salt)); MessageId sessionId = m.getId(); - Group g1 = introductionManager.getIntroductionGroup(c1); - Group g2 = introductionManager.getIntroductionGroup(c2); + Group g1 = introductionGroupFactory.createIntroductionGroup(c1); + Group g2 = introductionGroupFactory.createIntroductionGroup(c2); BdfDictionary d = new BdfDictionary(); d.put(SESSION_ID, sessionId); @@ -95,7 +98,9 @@ class IntroducerManager { d.put(AUTHOR_ID_2, c2.getAuthor().getId()); // save local state to database - clientHelper.addLocalMessage(txn, m, introductionManager.getClientId(), d, false); + clientHelper + .addLocalMessage(txn, m, IntroductionManagerImpl.CLIENT_ID, d, + false); return d; } @@ -143,7 +148,7 @@ class IntroducerManager { // send messages for (BdfDictionary d : result.toSend) { - introductionManager.sendMessage(txn, d); + messageSender.sendMessage(txn, d); } // broadcast events diff --git a/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java b/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..a255c8114538c0ad464e8a52eb6ecfee21400240 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java @@ -0,0 +1,30 @@ +package org.briarproject.introduction; + +import org.briarproject.api.clients.PrivateGroupFactory; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.sync.Group; + +import javax.inject.Inject; + +class IntroductionGroupFactory { + + final private PrivateGroupFactory privateGroupFactory; + final private Group localGroup; + + @Inject + IntroductionGroupFactory(PrivateGroupFactory privateGroupFactory) { + this.privateGroupFactory = privateGroupFactory; + localGroup = privateGroupFactory + .createLocalGroup(IntroductionManagerImpl.CLIENT_ID); + } + + public Group createIntroductionGroup(Contact c) { + return privateGroupFactory + .createPrivateGroup(IntroductionManagerImpl.CLIENT_ID, c); + } + + public Group createLocalGroup() { + return localGroup; + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index edec27bef53a4d1042639557c96d65f03f4bca06..3550d0148960c92e4677dea85e9180fa6aca70c5 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -3,25 +3,17 @@ package org.briarproject.introduction; import org.briarproject.api.FormatException; import org.briarproject.api.clients.Client; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.clients.MessageQueueManager; -import org.briarproject.api.clients.PrivateGroupFactory; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.BdfDictionary; -import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; -import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; -import org.briarproject.api.db.Metadata; import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.Transaction; -import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.introduction.IntroducerProtocolState; import org.briarproject.api.introduction.IntroductionManager; @@ -29,7 +21,6 @@ import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.introduction.SessionId; -import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; @@ -94,37 +85,22 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Logger.getLogger(IntroductionManagerImpl.class.getName()); private final DatabaseComponent db; - private final MessageQueueManager messageQueueManager; - private final PrivateGroupFactory privateGroupFactory; - private final MetadataEncoder metadataEncoder; private final IntroducerManager introducerManager; private final IntroduceeManager introduceeManager; - private final Group localGroup; + private final IntroductionGroupFactory introductionGroupFactory; @Inject - IntroductionManagerImpl(DatabaseComponent db, - MessageQueueManager messageQueueManager, - ClientHelper clientHelper, PrivateGroupFactory privateGroupFactory, - MetadataEncoder metadataEncoder, MetadataParser metadataParser, - CryptoComponent cryptoComponent, - TransportPropertyManager transportPropertyManager, - AuthorFactory authorFactory, ContactManager contactManager, - Clock clock) { + IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper, + MetadataParser metadataParser, Clock clock, + IntroducerManager introducerManager, + IntroduceeManager introduceeManager, + IntroductionGroupFactory introductionGroupFactory) { super(clientHelper, metadataParser, clock); this.db = db; - this.messageQueueManager = messageQueueManager; - this.privateGroupFactory = privateGroupFactory; - this.metadataEncoder = metadataEncoder; - // TODO: Inject these dependencies for easier testing - this.introducerManager = - new IntroducerManager(this, clientHelper, clock, - cryptoComponent); - this.introduceeManager = - new IntroduceeManager(db, this, clientHelper, clock, - cryptoComponent, transportPropertyManager, - authorFactory, contactManager); - localGroup = privateGroupFactory.createLocalGroup(CLIENT_ID); + this.introducerManager = introducerManager; + this.introduceeManager = introduceeManager; + this.introductionGroupFactory = introductionGroupFactory; } @Override @@ -134,7 +110,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook @Override public void createLocalState(Transaction txn) throws DbException { - db.addGroup(txn, localGroup); + db.addGroup(txn, introductionGroupFactory.createLocalGroup()); // Ensure we've set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @@ -143,7 +119,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook public void addingContact(Transaction txn, Contact c) throws DbException { try { // Create an introduction group for sending introduction messages - Group g = getIntroductionGroup(c); + Group g = introductionGroupFactory.createIntroductionGroup(c); // Return if we've already set things up for this contact if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact @@ -164,7 +140,8 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Long id = (long) c.getId().getInt(); try { Map<MessageId, BdfDictionary> map = clientHelper - .getMessageMetadataAsDictionary(txn, localGroup.getId()); + .getMessageMetadataAsDictionary(txn, + introductionGroupFactory.createLocalGroup().getId()); for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) { BdfDictionary d = entry.getValue(); long role = d.getLong(ROLE, -1L); @@ -185,7 +162,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook // remove the group (all messages will be removed with it) // this contact won't get our abort message, but the other will - db.removeGroup(txn, getIntroductionGroup(c)); + db.removeGroup(txn, introductionGroupFactory.createIntroductionGroup(c)); } /** @@ -287,8 +264,12 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Transaction txn = db.startTransaction(false); try { - introduceeManager - .acceptIntroduction(txn, contactId, sessionId, timestamp); + Contact c = db.getContact(txn, contactId); + Group g = introductionGroupFactory.createIntroductionGroup(c); + BdfDictionary state = + getSessionState(txn, g.getId(), sessionId.getBytes()); + + introduceeManager.acceptIntroduction(txn, state, timestamp); txn.setComplete(); } finally { db.endTransaction(txn); @@ -302,8 +283,12 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Transaction txn = db.startTransaction(false); try { - introduceeManager - .declineIntroduction(txn, contactId, sessionId, timestamp); + Contact c = db.getContact(txn, contactId); + Group g = introductionGroupFactory.createIntroductionGroup(c); + BdfDictionary state = + getSessionState(txn, g.getId(), sessionId.getBytes()); + + introduceeManager.declineIntroduction(txn, state, timestamp); txn.setComplete(); } finally { db.endTransaction(txn); @@ -322,8 +307,9 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook Transaction txn = db.startTransaction(true); try { // get messages and their status - GroupId g = - getIntroductionGroup(db.getContact(txn, contactId)).getId(); + GroupId g = introductionGroupFactory + .createIntroductionGroup(db.getContact(txn, contactId)) + .getId(); metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); statuses = db.getMessageStatus(txn, contactId, g); @@ -444,17 +430,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } } - @Override - public void setReadFlag(MessageId m, boolean read) throws DbException { - try { - BdfDictionary meta = BdfDictionary.of(new BdfEntry(READ, read)); - clientHelper.mergeMessageMetadata(m, meta); - } catch (FormatException e) { - throw new RuntimeException(e); - } - } - - public BdfDictionary getSessionState(Transaction txn, GroupId groupId, + private BdfDictionary getSessionState(Transaction txn, GroupId groupId, byte[] sessionId) throws DbException, FormatException { try { @@ -473,7 +449,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook // to find state for introducee Map<MessageId, BdfDictionary> map = clientHelper .getMessageMetadataAsDictionary(txn, - localGroup.getId()); + introductionGroupFactory.createLocalGroup().getId()); for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) { BdfDictionary state = m.getValue(); @@ -490,35 +466,11 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } } - public Group getIntroductionGroup(Contact c) { - return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); - } - - public Group getLocalGroup() { - return localGroup; - } - - public void sendMessage(Transaction txn, BdfDictionary message) - throws DbException, FormatException { - - BdfList bdfList = MessageEncoder.encodeMessage(message); - byte[] body = clientHelper.toByteArray(bdfList); - GroupId groupId = new GroupId(message.getRaw(GROUP_ID)); - Group group = db.getGroup(txn, groupId); - long timestamp = - message.getLong(MESSAGE_TIME, System.currentTimeMillis()); - message.put(MESSAGE_TIME, timestamp); - - Metadata metadata = metadataEncoder.encode(message); - - messageQueueManager - .sendMessage(txn, group, timestamp, body, metadata); - } - private void deleteMessage(Transaction txn, MessageId messageId) throws DbException { db.deleteMessage(txn, messageId); db.deleteMessageMetadata(txn, messageId); } + } diff --git a/briar-core/src/org/briarproject/introduction/IntroductionModule.java b/briar-core/src/org/briarproject/introduction/IntroductionModule.java index 4fb0e493df2349fbe75da4acbe4a25f5932e18ea..633d14c15bbf8c579b259eca808a19b65ee7bee1 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionModule.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionModule.java @@ -3,9 +3,13 @@ package org.briarproject.introduction; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.MessageQueueManager; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.system.Clock; import javax.inject.Inject; diff --git a/briar-core/src/org/briarproject/introduction/MessageEncoder.java b/briar-core/src/org/briarproject/introduction/MessageSender.java similarity index 52% rename from briar-core/src/org/briarproject/introduction/MessageEncoder.java rename to briar-core/src/org/briarproject/introduction/MessageSender.java index 12153122cd51323d3975cfcbcc38b48fac39c15a..7152a1344ea90ea0b6a622061fe299c0c8f82788 100644 --- a/briar-core/src/org/briarproject/introduction/MessageEncoder.java +++ b/briar-core/src/org/briarproject/introduction/MessageSender.java @@ -1,11 +1,25 @@ package org.briarproject.introduction; import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.system.Clock; + +import javax.inject.Inject; import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; import static org.briarproject.api.introduction.IntroductionConstants.MSG; import static org.briarproject.api.introduction.IntroductionConstants.NAME; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; @@ -18,9 +32,43 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; -public class MessageEncoder { +class MessageSender { + + final private DatabaseComponent db; + final private ClientHelper clientHelper; + final private Clock clock; + final private MetadataEncoder metadataEncoder; + final private MessageQueueManager messageQueueManager; + + @Inject + MessageSender(DatabaseComponent db, ClientHelper clientHelper, Clock clock, + MetadataEncoder metadataEncoder, + MessageQueueManager messageQueueManager) { + + this.db = db; + this.clientHelper = clientHelper; + this.clock = clock; + this.metadataEncoder = metadataEncoder; + this.messageQueueManager = messageQueueManager; + } + + public void sendMessage(Transaction txn, BdfDictionary message) + throws DbException, FormatException { + + BdfList bdfList = encodeMessage(message); + byte[] body = clientHelper.toByteArray(bdfList); + GroupId groupId = new GroupId(message.getRaw(GROUP_ID)); + Group group = db.getGroup(txn, groupId); + long timestamp = clock.currentTimeMillis(); + + message.put(MESSAGE_TIME, timestamp); + Metadata metadata = metadataEncoder.encode(message); + + messageQueueManager + .sendMessage(txn, group, timestamp, body, metadata); + } - public static BdfList encodeMessage(BdfDictionary d) + private BdfList encodeMessage(BdfDictionary d) throws FormatException { BdfList body; @@ -39,7 +87,7 @@ public class MessageEncoder { return body; } - private static BdfList encodeRequest(BdfDictionary d) + private BdfList encodeRequest(BdfDictionary d) throws FormatException { BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID), d.getString(NAME), d.getRaw(PUBLIC_KEY)); @@ -50,7 +98,7 @@ public class MessageEncoder { return list; } - private static BdfList encodeResponse(BdfDictionary d) + private BdfList encodeResponse(BdfDictionary d) throws FormatException { BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID), d.getBoolean(ACCEPT)); @@ -64,11 +112,11 @@ public class MessageEncoder { return list; } - private static BdfList encodeAck(BdfDictionary d) throws FormatException { + private BdfList encodeAck(BdfDictionary d) throws FormatException { return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID)); } - private static BdfList encodeAbort(BdfDictionary d) throws FormatException { + private BdfList encodeAbort(BdfDictionary d) throws FormatException { return BdfList.of(TYPE_ABORT, d.getRaw(SESSION_ID)); } diff --git a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..455c93603259bc875a5f05f53d5ba85405df72fd --- /dev/null +++ b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java @@ -0,0 +1,283 @@ +package org.briarproject.introduction; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.Bytes; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroduceeProtocolState; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.properties.TransportPropertyManager; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Test; + +import java.security.SecureRandom; + +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.EXISTS; +import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER; +import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.STATE; +import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class IntroduceeManagerTest extends BriarTestCase { + + final Mockery context; + final IntroduceeManager introduceeManager; + final DatabaseComponent db; + final CryptoComponent cryptoComponent; + final ClientHelper clientHelper; + final IntroductionGroupFactory introductionGroupFactory; + final MessageSender messageSender; + final TransportPropertyManager transportPropertyManager; + final AuthorFactory authorFactory; + final ContactManager contactManager; + final Clock clock; + final Contact introducer; + final Contact introducee1; + final Contact introducee2; + final Group localGroup1; + final Group introductionGroup1; + final Group introductionGroup2; + final Transaction txn; + final long time = 42L; + final Message localStateMessage; + final ClientId clientId; + final SessionId sessionId; + final Message message1; + + public IntroduceeManagerTest() { + context = new Mockery(); + context.setImposteriser(ClassImposteriser.INSTANCE); + messageSender = context.mock(MessageSender.class); + db = context.mock(DatabaseComponent.class); + cryptoComponent = context.mock(CryptoComponent.class); + clientHelper = context.mock(ClientHelper.class); + clock = context.mock(Clock.class); + introductionGroupFactory = + context.mock(IntroductionGroupFactory.class); + transportPropertyManager = context.mock(TransportPropertyManager.class); + authorFactory = context.mock(AuthorFactory.class); + contactManager = context.mock(ContactManager.class); + + introduceeManager = new IntroduceeManager(messageSender, db, + clientHelper, clock, cryptoComponent, transportPropertyManager, + authorFactory, contactManager, introductionGroupFactory); + + AuthorId authorId0 = new AuthorId(TestUtils.getRandomId()); + Author author0 = new Author(authorId0, "Introducer", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + AuthorId localAuthorId = new AuthorId(TestUtils.getRandomId()); + ContactId contactId0 = new ContactId(234); + introducer = new Contact(contactId0, author0, localAuthorId, true); + + AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); + Author author1 = new Author(authorId1, "Introducee1", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); + ContactId contactId1 = new ContactId(234); + introducee1 = new Contact(contactId1, author1, localAuthorId1, true); + + AuthorId authorId2 = new AuthorId(TestUtils.getRandomId()); + Author author2 = new Author(authorId2, "Introducee2", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + ContactId contactId2 = new ContactId(235); + introducee2 = new Contact(contactId2, author2, localAuthorId, true); + + clientId = IntroductionManagerImpl.CLIENT_ID; + localGroup1 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + + sessionId = new SessionId(TestUtils.getRandomId()); + localStateMessage = new Message( + new MessageId(TestUtils.getRandomId()), + localGroup1.getId(), + time, + TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1) + ); + message1 = new Message( + new MessageId(TestUtils.getRandomId()), + introductionGroup1.getId(), + time, + TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1) + ); + + txn = new Transaction(null, false); + } + + @Test + public void testIncomingRequestMessage() + throws DbException, FormatException { + + final BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_REQUEST); + msg.put(GROUP_ID, introductionGroup1.getId()); + msg.put(SESSION_ID, sessionId); + msg.put(MESSAGE_ID, message1.getId()); + msg.put(MESSAGE_TIME, time); + msg.put(NAME, introducee2.getAuthor().getName()); + msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); + + final BdfDictionary state = + initializeSessionState(txn, introductionGroup1.getId(), msg); + + context.checking(new Expectations() {{ + oneOf(clientHelper).mergeMessageMetadata(txn, + localStateMessage.getId(), state); + }}); + + introduceeManager.incomingMessage(txn, state, msg); + + context.assertIsSatisfied(); + + assertFalse(txn.isComplete()); + } + + @Test + public void testIncomingResponseMessage() + throws DbException, FormatException { + + final BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_RESPONSE); + msg.put(GROUP_ID, introductionGroup1.getId()); + msg.put(SESSION_ID, sessionId); + msg.put(MESSAGE_ID, message1.getId()); + msg.put(MESSAGE_TIME, time); + msg.put(NAME, introducee2.getAuthor().getName()); + msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); + + final BdfDictionary state = + initializeSessionState(txn, introductionGroup1.getId(), msg); + state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal()); + + // turn request message into a response + msg.put(ACCEPT, true); + msg.put(TIME, time); + msg.put(E_PUBLIC_KEY, TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + msg.put(TRANSPORT, new BdfDictionary()); + + context.checking(new Expectations() {{ + oneOf(clientHelper).mergeMessageMetadata(txn, + localStateMessage.getId(), state); + }}); + + introduceeManager.incomingMessage(txn, state, msg); + + context.assertIsSatisfied(); + + assertFalse(txn.isComplete()); + } + + private BdfDictionary initializeSessionState(final Transaction txn, + final GroupId groupId, final BdfDictionary msg) + throws DbException, FormatException { + + final SecureRandom secureRandom = context.mock(SecureRandom.class); + final Bytes salt = new Bytes(new byte[64]); + final BdfDictionary groupMetadata = BdfDictionary.of( + new BdfEntry(CONTACT, introducee1.getId().getInt()) + ); + final boolean contactExists = true; + final BdfDictionary state = new BdfDictionary(); + state.put(STORAGE_ID, localStateMessage.getId()); + state.put(STATE, AWAIT_REQUEST.getValue()); + state.put(ROLE, ROLE_INTRODUCEE); + state.put(GROUP_ID, groupId); + state.put(INTRODUCER, introducer.getAuthor().getName()); + state.put(CONTACT_ID_1, introducer.getId().getInt()); + state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes()); + state.put(NOT_OUR_RESPONSE, localStateMessage.getId()); + state.put(ANSWERED, false); + state.put(EXISTS, true); + state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId()); + + context.checking(new Expectations() {{ + oneOf(clock).currentTimeMillis(); + will(returnValue(time)); + oneOf(cryptoComponent).getSecureRandom(); + will(returnValue(secureRandom)); + oneOf(secureRandom).nextBytes(salt.getBytes()); + oneOf(introductionGroupFactory).createLocalGroup(); + will(returnValue(localGroup1)); + oneOf(clientHelper) + .createMessage(localGroup1.getId(), time, BdfList.of(salt)); + will(returnValue(localStateMessage)); + + // who is making the introduction? who is the introducer? + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + groupId); + will(returnValue(groupMetadata)); + oneOf(db).getContact(txn, introducer.getId()); + will(returnValue(introducer)); + + // create remote author to check if contact exists + oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(), + introducee2.getAuthor().getPublicKey()); + will(returnValue(introducee2.getAuthor())); + oneOf(contactManager) + .contactExists(txn, introducee2.getAuthor().getId(), + introducer.getLocalAuthorId()); + will(returnValue(contactExists)); + + // store session state + oneOf(clientHelper) + .addLocalMessage(txn, localStateMessage, clientId, state, + false); + }}); + + BdfDictionary result = introduceeManager.initialize(txn, groupId, msg); + + context.assertIsSatisfied(); + return result; + } + +} diff --git a/briar-tests/src/org/briarproject/introduction/IntroducerManagerTest.java b/briar-tests/src/org/briarproject/introduction/IntroducerManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9f4cc21746de77fe94fa1d63e75171ffc9d28d9e --- /dev/null +++ b/briar-tests/src/org/briarproject/introduction/IntroducerManagerTest.java @@ -0,0 +1,189 @@ +package org.briarproject.introduction; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.Bytes; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Test; + +import java.security.SecureRandom; + +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES; +import static org.briarproject.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS; +import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_1; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_2; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCER; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.STATE; +import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.junit.Assert.assertFalse; + +public class IntroducerManagerTest extends BriarTestCase { + + final Mockery context; + final IntroducerManager introducerManager; + final CryptoComponent cryptoComponent; + final ClientHelper clientHelper; + final IntroductionGroupFactory introductionGroupFactory; + final MessageSender messageSender; + final Clock clock; + final Contact introducee1; + final Contact introducee2; + final Group localGroup0; + final Group introductionGroup1; + final Group introductionGroup2; + + public IntroducerManagerTest() { + context = new Mockery(); + context.setImposteriser(ClassImposteriser.INSTANCE); + messageSender = context.mock(MessageSender.class); + cryptoComponent = context.mock(CryptoComponent.class); + clientHelper = context.mock(ClientHelper.class); + clock = context.mock(Clock.class); + introductionGroupFactory = + context.mock(IntroductionGroupFactory.class); + + introducerManager = + new IntroducerManager(messageSender, clientHelper, clock, + cryptoComponent, introductionGroupFactory); + + AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); + Author author1 = new Author(authorId1, "Introducee1", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); + ContactId contactId1 = new ContactId(234); + introducee1 = new Contact(contactId1, author1, localAuthorId1, true); + + AuthorId authorId2 = new AuthorId(TestUtils.getRandomId()); + Author author2 = new Author(authorId2, "Introducee2", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId()); + ContactId contactId2 = new ContactId(235); + introducee2 = new Contact(contactId2, author2, localAuthorId2, true); + + localGroup0 = new Group(new GroupId(TestUtils.getRandomId()), + getClientId(), new byte[0]); + introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()), + getClientId(), new byte[0]); + introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()), + getClientId(), new byte[0]); + + context.assertIsSatisfied(); + } + + @Test + public void testMakeIntroduction() throws DbException, FormatException { + final Transaction txn = new Transaction(null, false); + final long time = 42L; + context.setImposteriser(ClassImposteriser.INSTANCE); + final SecureRandom secureRandom = context.mock(SecureRandom.class); + final Bytes salt = new Bytes(new byte[64]); + final Message msg = new Message(new MessageId(TestUtils.getRandomId()), + localGroup0.getId(), time, TestUtils.getRandomBytes(64)); + final BdfDictionary state = new BdfDictionary(); + state.put(SESSION_ID, msg.getId()); + state.put(STORAGE_ID, msg.getId()); + state.put(STATE, PREPARE_REQUESTS.getValue()); + state.put(ROLE, ROLE_INTRODUCER); + state.put(GROUP_ID_1, introductionGroup1.getId()); + state.put(GROUP_ID_2, introductionGroup2.getId()); + state.put(CONTACT_1, introducee1.getAuthor().getName()); + state.put(CONTACT_2, introducee2.getAuthor().getName()); + state.put(CONTACT_ID_1, introducee1.getId().getInt()); + state.put(CONTACT_ID_2, introducee2.getId().getInt()); + state.put(AUTHOR_ID_1, introducee1.getAuthor().getId()); + state.put(AUTHOR_ID_2, introducee2.getAuthor().getId()); + final BdfDictionary state2 = (BdfDictionary) state.clone(); + state2.put(STATE, AWAIT_RESPONSES.getValue()); + + final BdfDictionary msg1 = new BdfDictionary(); + msg1.put(TYPE, TYPE_REQUEST); + msg1.put(SESSION_ID, state.getRaw(SESSION_ID)); + msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1)); + msg1.put(NAME, state.getString(CONTACT_2)); + msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); + final BdfDictionary msg1send = (BdfDictionary) msg1.clone(); + msg1send.put(MESSAGE_TIME, time); + + final BdfDictionary msg2 = new BdfDictionary(); + msg2.put(TYPE, TYPE_REQUEST); + msg2.put(SESSION_ID, state.getRaw(SESSION_ID)); + msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2)); + msg2.put(NAME, state.getString(CONTACT_1)); + msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey()); + final BdfDictionary msg2send = (BdfDictionary) msg2.clone(); + msg2send.put(MESSAGE_TIME, time); + + context.checking(new Expectations() {{ + // initialize and store session state + oneOf(clock).currentTimeMillis(); + will(returnValue(time)); + oneOf(cryptoComponent).getSecureRandom(); + will(returnValue(secureRandom)); + oneOf(secureRandom).nextBytes(salt.getBytes()); + oneOf(introductionGroupFactory).createLocalGroup(); + will(returnValue(localGroup0)); + oneOf(clientHelper).createMessage(localGroup0.getId(), time, + BdfList.of(salt)); + will(returnValue(msg)); + oneOf(introductionGroupFactory) + .createIntroductionGroup(introducee1); + will(returnValue(introductionGroup1)); + oneOf(introductionGroupFactory) + .createIntroductionGroup(introducee2); + will(returnValue(introductionGroup2)); + oneOf(clientHelper).addLocalMessage(txn, msg, getClientId(), state, + false); + + // send message + oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2); + oneOf(messageSender).sendMessage(txn, msg1send); + oneOf(messageSender).sendMessage(txn, msg2send); + }}); + + introducerManager + .makeIntroduction(txn, introducee1, introducee2, null, time); + + context.assertIsSatisfied(); + + assertFalse(txn.isComplete()); + } + + private ClientId getClientId() { + return IntroductionManagerImpl.CLIENT_ID; + } + +} diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f6b5e90e88d5f375bc7a325722873ef9abf2c1b9 --- /dev/null +++ b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java @@ -0,0 +1,287 @@ +package org.briarproject.introduction; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; +import org.briarproject.api.clients.PrivateGroupFactory; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.data.MetadataParser; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.sync.MessageStatus; +import org.briarproject.api.system.Clock; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Test; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import static junit.framework.TestCase.assertTrue; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCER; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.junit.Assert.assertFalse; + +public class IntroductionManagerImplTest extends BriarTestCase { + + final Mockery context; + final IntroductionManagerImpl introductionManager; + final IntroducerManager introducerManager; + final IntroduceeManager introduceeManager; + final DatabaseComponent db; + final PrivateGroupFactory privateGroupFactory; + final ClientHelper clientHelper; + final MetadataEncoder metadataEncoder; + final MessageQueueManager messageQueueManager; + final IntroductionGroupFactory introductionGroupFactory; + final Clock clock; + final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + final long time = 42L; + final Contact introducee1; + final Contact introducee2; + final Group localGroup0; + final Group introductionGroup1; + final Group introductionGroup2; + final Message message1; + Transaction txn; + + public IntroductionManagerImplTest() { + AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); + Author author1 = new Author(authorId1, "Introducee1", + new byte[MAX_PUBLIC_KEY_LENGTH]); + AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); + ContactId contactId1 = new ContactId(234); + introducee1 = new Contact(contactId1, author1, localAuthorId1, true); + + AuthorId authorId2 = new AuthorId(TestUtils.getRandomId()); + Author author2 = new Author(authorId2, "Introducee2", + new byte[MAX_PUBLIC_KEY_LENGTH]); + AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId()); + ContactId contactId2 = new ContactId(235); + introducee2 = new Contact(contactId2, author2, localAuthorId2, true); + + ClientId clientId = new ClientId(TestUtils.getRandomId()); + localGroup0 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()), + clientId, new byte[0]); + + message1 = new Message( + new MessageId(TestUtils.getRandomId()), + introductionGroup1.getId(), + time, + TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1) + ); + + // mock ALL THE THINGS!!! + context = new Mockery(); + context.setImposteriser(ClassImposteriser.INSTANCE); + introducerManager = context.mock(IntroducerManager.class); + introduceeManager = context.mock(IntroduceeManager.class); + db = context.mock(DatabaseComponent.class); + privateGroupFactory = context.mock(PrivateGroupFactory.class); + clientHelper = context.mock(ClientHelper.class); + metadataEncoder = + context.mock(MetadataEncoder.class); + messageQueueManager = + context.mock(MessageQueueManager.class); + MetadataParser metadataParser = context.mock(MetadataParser.class); + introductionGroupFactory = context.mock(IntroductionGroupFactory.class); + clock = context.mock(Clock.class); + + introductionManager = new IntroductionManagerImpl( + db, clientHelper, metadataParser, clock, introducerManager, + introduceeManager, introductionGroupFactory + ); + } + + @Test + public void testMakeIntroduction() throws DbException, FormatException { + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(introducerManager) + .makeIntroduction(txn, introducee1, introducee2, null, + time); + oneOf(db).endTransaction(txn); + }}); + + introductionManager + .makeIntroduction(introducee1, introducee2, null, time); + + context.assertIsSatisfied(); + assertTrue(txn.isComplete()); + } + + @Test + public void testAcceptIntroduction() throws DbException, FormatException { + final BdfDictionary state = BdfDictionary.of( + new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), + new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) + ); + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, introducee1.getId()); + will(returnValue(introducee1)); + oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); + will(returnValue(introductionGroup1)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); + will(returnValue(state)); + oneOf(introduceeManager).acceptIntroduction(txn, state, time); + oneOf(db).endTransaction(txn); + }}); + + introductionManager + .acceptIntroduction(introducee1.getId(), sessionId, time); + + context.assertIsSatisfied(); + assertTrue(txn.isComplete()); + } + + @Test + public void testDeclineIntroduction() throws DbException, FormatException { + final BdfDictionary state = BdfDictionary.of( + new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), + new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) + ); + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, introducee1.getId()); + will(returnValue(introducee1)); + oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); + will(returnValue(introductionGroup1)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); + will(returnValue(state)); + oneOf(introduceeManager).declineIntroduction(txn, state, time); + oneOf(db).endTransaction(txn); + }}); + + introductionManager + .declineIntroduction(introducee1.getId(), sessionId, time); + + context.assertIsSatisfied(); + assertTrue(txn.isComplete()); + } + + @Test + public void testGetIntroductionMessages() + throws DbException, FormatException { + + final Map<MessageId, BdfDictionary> metadata = Collections.emptyMap(); + final Collection<MessageStatus> statuses = Collections.emptyList(); + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(true); + will(returnValue(txn)); + oneOf(db).getContact(txn, introducee1.getId()); + will(returnValue(introducee1)); + oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); + will(returnValue(introductionGroup1)); + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + introductionGroup1.getId()); + will(returnValue(metadata)); + oneOf(db).getMessageStatus(txn, introducee1.getId(), + introductionGroup1.getId()); + will(returnValue(statuses)); + oneOf(db).endTransaction(txn); + }}); + + introductionManager.getIntroductionMessages(introducee1.getId()); + + context.assertIsSatisfied(); + assertTrue(txn.isComplete()); + } + + @Test + public void testIncomingRequestMessage() + throws DbException, FormatException { + + final BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_REQUEST); + + final BdfDictionary state = new BdfDictionary(); + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(introduceeManager) + .initialize(txn, introductionGroup1.getId(), msg); + will(returnValue(state)); + oneOf(introduceeManager) + .incomingMessage(txn, state, msg); + }}); + + introductionManager + .incomingMessage(txn, message1, new BdfList(), msg); + + context.assertIsSatisfied(); + assertFalse(txn.isComplete()); + } + + @Test + public void testIncomingResponseMessage() + throws DbException, FormatException { + + final BdfDictionary msg = BdfDictionary.of( + new BdfEntry(TYPE, TYPE_RESPONSE), + new BdfEntry(SESSION_ID, sessionId) + ); + + final BdfDictionary state = new BdfDictionary(); + state.put(ROLE, ROLE_INTRODUCER); + state.put(GROUP_ID_1, introductionGroup1.getId()); + state.put(GROUP_ID_2, introductionGroup2.getId()); + + txn = new Transaction(null, false); + + context.checking(new Expectations() {{ + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); + will(returnValue(state)); + oneOf(introducerManager).incomingMessage(txn, state, msg); + }}); + + introductionManager + .incomingMessage(txn, message1, new BdfList(), msg); + + context.assertIsSatisfied(); + assertFalse(txn.isComplete()); + } + + +} diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dbacbc8f740273e4f6bf1988406ab0cccf00eee2 --- /dev/null +++ b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java @@ -0,0 +1,357 @@ +package org.briarproject.introduction; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.FormatException; +import org.briarproject.api.TransportId; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; +import org.briarproject.system.SystemClock; +import org.jmock.Mockery; +import org.junit.Test; + +import java.io.IOException; + +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.MSG; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; +import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class IntroductionValidatorTest extends BriarTestCase { + + private final Mockery context = new Mockery(); + private final Group group; + private final Message message; + private final IntroductionValidator validator; + private final Clock clock = new SystemClock(); + + public IntroductionValidatorTest() { + GroupId groupId = new GroupId(TestUtils.getRandomId()); + ClientId clientId = new ClientId(TestUtils.getRandomId()); + byte[] descriptor = TestUtils.getRandomBytes(12); + group = new Group(groupId, clientId, descriptor); + + MessageId messageId = new MessageId(TestUtils.getRandomId()); + long timestamp = System.currentTimeMillis(); + byte[] raw = TestUtils.getRandomBytes(123); + message = new Message(messageId, group.getId(), timestamp, raw); + + + ClientHelper clientHelper = context.mock(ClientHelper.class); + MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); + validator = new IntroductionValidator(clientHelper, metadataEncoder, + clock); + context.assertIsSatisfied(); + } + + // + // Introduction Requests + // + + @Test + public void testValidateProperIntroductionRequest() throws IOException { + final byte[] sessionId = TestUtils.getRandomId(); + final String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH); + final byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + final String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH); + + BdfList body = BdfList.of(TYPE_REQUEST, sessionId, + name, publicKey, text); + + final BdfDictionary result = + validator.validateMessage(message, group, body); + + assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE)); + assertEquals(sessionId, result.getRaw(SESSION_ID)); + assertEquals(name, result.getString(NAME)); + assertEquals(publicKey, result.getRaw(PUBLIC_KEY)); + assertEquals(text, result.getString(MSG)); + context.assertIsSatisfied(); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionRequestWithNoName() throws IOException { + BdfDictionary msg = getValidIntroductionRequest(); + + // no NAME is message + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getRaw(PUBLIC_KEY)); + if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); + + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionRequestWithLongName() throws IOException { + // too long NAME in message + BdfDictionary msg = getValidIntroductionRequest(); + msg.put(NAME, msg.get(NAME) + "x"); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getString(NAME), msg.getRaw(PUBLIC_KEY)); + if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); + + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionRequestWithWrongType() + throws IOException { + // wrong message type + BdfDictionary msg = getValidIntroductionRequest(); + msg.put(TYPE, 324234); + + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getString(NAME), msg.getRaw(PUBLIC_KEY)); + if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); + validator.validateMessage(message, group, body); + } + + private BdfDictionary getValidIntroductionRequest() throws FormatException { + byte[] sessionId = TestUtils.getRandomId(); + String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH); + byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH); + + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_REQUEST); + msg.put(SESSION_ID, sessionId); + msg.put(NAME, name); + msg.put(PUBLIC_KEY, publicKey); + msg.put(MSG, text); + + return msg; + } + + // + // Introduction Responses + // + + @Test + public void testValidateIntroductionAcceptResponse() throws IOException { + byte[] groupId = TestUtils.getRandomId(); + byte[] sessionId = TestUtils.getRandomId(); + long time = clock.currentTimeMillis(); + byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + String transportId = TestUtils + .getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH); + BdfDictionary tProps = BdfDictionary.of( + new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH), + TestUtils.getRandomString(MAX_PROPERTY_LENGTH)) + ); + BdfDictionary tp = BdfDictionary.of( + new BdfEntry(transportId, tProps) + ); + + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_RESPONSE); + msg.put(GROUP_ID, groupId); + msg.put(SESSION_ID, sessionId); + msg.put(ACCEPT, true); + msg.put(TIME, time); + msg.put(E_PUBLIC_KEY, publicKey); + msg.put(TRANSPORT, tp); + + BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID), + msg.getBoolean(ACCEPT), msg.getLong(TIME), + msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT)); + + final BdfDictionary result = + validator.validateMessage(message, group, body); + + assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE)); + assertEquals(sessionId, result.getRaw(SESSION_ID)); + assertEquals(true, result.getBoolean(ACCEPT)); + assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY)); + assertEquals(tp, result.getDictionary(TRANSPORT)); + context.assertIsSatisfied(); + } + + @Test + public void testValidateIntroductionDeclineResponse() + throws IOException { + BdfDictionary msg = getValidIntroductionResponse(false); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getBoolean(ACCEPT)); + + BdfDictionary result = validator.validateMessage(message, group, body); + + assertFalse(result.getBoolean(ACCEPT)); + context.assertIsSatisfied(); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionResponseWithoutAccept() + throws IOException { + BdfDictionary msg = getValidIntroductionResponse(false); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); + + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionResponseWithBrokenTp() + throws IOException { + BdfDictionary msg = getValidIntroductionResponse(true); + BdfDictionary tp = msg.getDictionary(TRANSPORT); + tp.put(TestUtils + .getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X"); + msg.put(TRANSPORT, tp); + + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getBoolean(ACCEPT), msg.getLong(TIME), + msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT)); + + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionResponseWithoutPublicKey() + throws IOException { + BdfDictionary msg = getValidIntroductionResponse(true); + + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getBoolean(ACCEPT), msg.getLong(TIME), + msg.getDictionary(TRANSPORT)); + + validator.validateMessage(message, group, body); + } + + private BdfDictionary getValidIntroductionResponse(boolean accept) + throws FormatException { + + byte[] groupId = TestUtils.getRandomId(); + byte[] sessionId = TestUtils.getRandomId(); + long time = clock.currentTimeMillis(); + byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + String transportId = TestUtils + .getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH); + BdfDictionary tProps = BdfDictionary.of( + new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH), + TestUtils.getRandomString(MAX_PROPERTY_LENGTH)) + ); + BdfDictionary tp = BdfDictionary.of( + new BdfEntry(transportId, tProps) + ); + + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_RESPONSE); + msg.put(GROUP_ID, groupId); + msg.put(SESSION_ID, sessionId); + msg.put(ACCEPT, accept); + if (accept) { + msg.put(TIME, time); + msg.put(E_PUBLIC_KEY, publicKey); + msg.put(TRANSPORT, tp); + } + + return msg; + } + + // + // Introduction ACK + // + + @Test + public void testValidateProperIntroductionAck() throws IOException { + final byte[] sessionId = TestUtils.getRandomId(); + + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_ACK); + msg.put(SESSION_ID, sessionId); + + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); + + BdfDictionary result = + validator.validateMessage(message, group, body); + + assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE)); + assertEquals(sessionId, result.getRaw(SESSION_ID)); + context.assertIsSatisfied(); + } + + @Test(expected = FormatException.class) + public void testValidateTooLongIntroductionAck() throws IOException { + BdfDictionary msg = BdfDictionary.of( + new BdfEntry(TYPE, TYPE_ACK), + new BdfEntry(SESSION_ID, TestUtils.getRandomId()), + new BdfEntry("garbage", TestUtils.getRandomString(255)) + ); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getString("garbage")); + + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testValidateIntroductionAckWithLongSessionId() throws IOException { + BdfDictionary msg = BdfDictionary.of( + new BdfEntry(TYPE, TYPE_ACK), + new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1]) + ); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); + + validator.validateMessage(message, group, body); + } + + // + // Introduction Abort + // + + @Test + public void testValidateProperIntroductionAbort() throws IOException { + byte[] sessionId = TestUtils.getRandomId(); + + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, TYPE_ABORT); + msg.put(SESSION_ID, sessionId); + + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); + + BdfDictionary result = + validator.validateMessage(message, group, body); + + assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE)); + assertEquals(sessionId, result.getRaw(SESSION_ID)); + context.assertIsSatisfied(); + } + + @Test(expected = FormatException.class) + public void testValidateTooLongIntroductionAbort() throws IOException { + BdfDictionary msg = BdfDictionary.of( + new BdfEntry(TYPE, TYPE_ABORT), + new BdfEntry(SESSION_ID, TestUtils.getRandomId()), + new BdfEntry("garbage", TestUtils.getRandomString(255)) + ); + BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), + msg.getString("garbage")); + + validator.validateMessage(message, group, body); + } + +} diff --git a/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1c9d3b82c72e5de6d9b3a84db37871be524f4e7e --- /dev/null +++ b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java @@ -0,0 +1,95 @@ +package org.briarproject.introduction; + +import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; +import org.briarproject.api.clients.PrivateGroupFactory; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.system.Clock; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Test; + +import static junit.framework.Assert.assertFalse; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; + +public class MessageSenderTest extends BriarTestCase { + + final Mockery context; + final MessageSender messageSender; + final DatabaseComponent db; + final PrivateGroupFactory privateGroupFactory; + final ClientHelper clientHelper; + final MetadataEncoder metadataEncoder; + final MessageQueueManager messageQueueManager; + final Clock clock; + + public MessageSenderTest() { + context = new Mockery(); + db = context.mock(DatabaseComponent.class); + privateGroupFactory = context.mock(PrivateGroupFactory.class); + clientHelper = context.mock(ClientHelper.class); + metadataEncoder = + context.mock(MetadataEncoder.class); + messageQueueManager = + context.mock(MessageQueueManager.class); + clock = context.mock(Clock.class); + + messageSender = + new MessageSender(db, clientHelper, clock, metadataEncoder, + messageQueueManager); + } + + @Test + public void testSendMessage() throws DbException, FormatException { + final Transaction txn = new Transaction(null, false); + final Group privateGroup = new Group(new GroupId(TestUtils.getRandomId()), + new ClientId(TestUtils.getRandomId()), new byte[0]); + final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + final long time = 42L; + final BdfDictionary msg = BdfDictionary.of( + new BdfEntry(TYPE, TYPE_ACK), + new BdfEntry(GROUP_ID, privateGroup.getId()), + new BdfEntry(SESSION_ID, sessionId) + ); + final BdfList bodyList = + BdfList.of(TYPE_ACK, msg.getRaw(SESSION_ID)); + final byte[] body = TestUtils.getRandomBytes(8); + final Metadata metadata = new Metadata(); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toByteArray(bodyList); + will(returnValue(body)); + oneOf(db).getGroup(txn, privateGroup.getId()); + will(returnValue(privateGroup)); + oneOf(metadataEncoder).encode(msg); + will(returnValue(metadata)); + oneOf(clock).currentTimeMillis(); + will(returnValue(time)); + oneOf(messageQueueManager) + .sendMessage(txn, privateGroup, time, body, metadata); + }}); + + messageSender.sendMessage(txn, msg); + + context.assertIsSatisfied(); + assertFalse(txn.isComplete()); + } + +}