From 62527a62c14cbc827c66e542588573cfde613564 Mon Sep 17 00:00:00 2001 From: Santiago Torres Date: Tue, 24 May 2016 01:41:35 -0400 Subject: [PATCH] WIP: Replace session states in the Engine classes --- .../IntroductionIntegrationTest.java | 5 +- .../introduction/IntroductionConstants.java | 1 + .../introduction/IntroduceeEngine.java | 199 +++--- .../introduction/IntroduceeManager.java | 163 +++-- .../introduction/IntroduceeSessionState.java | 584 ++++++++++++++++++ .../introduction/IntroducerEngine.java | 157 +++-- .../introduction/IntroducerManager.java | 61 +- .../introduction/IntroducerSessionState.java | 210 +++++++ .../introduction/IntroductionManagerImpl.java | 185 +++--- .../introduction/IntroductionState.java | 64 ++ .../introduction/IntroduceeManagerTest.java | 145 +++-- .../introduction/IntroducerManagerTest.java | 151 +++-- .../IntroductionManagerImplTest.java | 172 +++++- 13 files changed, 1584 insertions(+), 513 deletions(-) create mode 100644 briar-core/src/org/briarproject/introduction/IntroduceeSessionState.java create mode 100644 briar-core/src/org/briarproject/introduction/IntroducerSessionState.java create mode 100644 briar-core/src/org/briarproject/introduction/IntroductionState.java diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java index c92d588f0..d0396d76e 100644 --- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java @@ -300,7 +300,7 @@ public class IntroductionIntegrationTest extends BriarTestCase { assertTrue(listener0.response2Received); // sync first forwarded response - deliverMessage(sync0, contactId0, sync2, contactId2); + deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2"); // note how the introducer does not forward the second response, // because after the first decline the protocol finished @@ -1141,7 +1141,8 @@ public class IntroductionIntegrationTest extends BriarTestCase { time); } } - } catch (DbException | IOException exception) { + } catch (DbException | IOException | NullPointerException exception) { + msgWaiter.rethrow(exception); eventWaiter.rethrow(exception); } finally { eventWaiter.resume(); diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java index ac8975e5c..a7be3a320 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -71,6 +71,7 @@ public interface IntroductionConstants { String OUR_SIGNATURE = "ourSignature"; String TASK = "task"; + int NO_TASK = -1; int TASK_ADD_CONTACT = 0; int TASK_ACTIVATE_CONTACT = 1; int TASK_ABORT = 2; diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java index 8f77539c7..b022c49ed 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java @@ -2,6 +2,7 @@ package org.briarproject.introduction; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ProtocolEngine; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.event.Event; @@ -11,7 +12,6 @@ import org.briarproject.api.identity.AuthorId; import org.briarproject.api.introduction.IntroduceeAction; import org.briarproject.api.introduction.IntroduceeProtocolState; import org.briarproject.api.introduction.IntroductionRequest; -import org.briarproject.api.clients.SessionId; import org.briarproject.api.sync.MessageId; import java.util.ArrayList; @@ -34,9 +34,6 @@ import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_RE import static org.briarproject.api.introduction.IntroduceeProtocolState.ERROR; import static org.briarproject.api.introduction.IntroduceeProtocolState.FINISHED; import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED; -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; @@ -51,8 +48,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE; import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME; 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.REMOTE_AUTHOR_IS_US; 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.SIGNATURE; @@ -69,23 +64,23 @@ 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 IntroduceeEngine - implements ProtocolEngine { +class IntroduceeEngine + implements ProtocolEngine { private static final Logger LOG = Logger.getLogger(IntroduceeEngine.class.getName()); @Override - public StateUpdate onLocalAction( - BdfDictionary localState, BdfDictionary localAction) { + public StateUpdate onLocalAction( + IntroduceeSessionState localState, BdfDictionary localAction) { try { - IntroduceeProtocolState currentState = - getState(localState.getLong(STATE)); + IntroduceeProtocolState currentState = localState.getState(); int type = localAction.getLong(TYPE).intValue(); IntroduceeAction action; - if (localState.containsKey(ACCEPT)) action = IntroduceeAction - .getLocal(type, localState.getBoolean(ACCEPT)); + // FIXME: discuss? used to be: if has key ACCEPT: + if (localState.wasAccepted()) action = IntroduceeAction + .getLocal(type, localState.getAccept()); else action = IntroduceeAction.getLocal(type); IntroduceeProtocolState nextState = currentState.next(action); @@ -104,17 +99,17 @@ public class IntroduceeEngine List messages = new ArrayList(1); if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { - localState.put(STATE, nextState.getValue()); - localState.put(ANSWERED, true); + localState.setState(nextState); + localState.setAnswered(true); // create the introduction response message BdfDictionary msg = new BdfDictionary(); msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); - msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg.put(ACCEPT, localState.getBoolean(ACCEPT)); - if (localState.getBoolean(ACCEPT)) { - msg.put(TIME, localState.getLong(OUR_TIME)); - msg.put(E_PUBLIC_KEY, localState.getRaw(OUR_PUBLIC_KEY)); + msg.put(GROUP_ID, localState.getIntroductionGroupId()); + msg.put(SESSION_ID, localState.getSessionId()); + msg.put(ACCEPT, localState.getAccept()); + if (localState.getAccept()) { + msg.put(TIME, localState.getOurTime()); + msg.put(E_PUBLIC_KEY, localState.getOurPublicKey()); msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT)); } msg.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME)); @@ -122,7 +117,7 @@ public class IntroduceeEngine logAction(currentState, localState, msg); if (nextState == AWAIT_ACK) { - localState.put(TASK, TASK_ADD_CONTACT); + localState.setTask(TASK_ADD_CONTACT); } } else if (action == ACK) { // just send ACK, don't update local state again @@ -132,7 +127,7 @@ public class IntroduceeEngine throw new IllegalArgumentException(); } List events = Collections.emptyList(); - return new StateUpdate(false, + return new StateUpdate(false, false, localState, messages, events); } catch (FormatException e) { @@ -141,12 +136,11 @@ public class IntroduceeEngine } @Override - public StateUpdate onMessageReceived( - BdfDictionary localState, BdfDictionary msg) { + public StateUpdate onMessageReceived( + IntroduceeSessionState localState, BdfDictionary msg) { try { - IntroduceeProtocolState currentState = - getState(localState.getLong(STATE)); + IntroduceeProtocolState currentState = localState.getState(); int type = msg.getLong(TYPE).intValue(); IntroduceeAction action = IntroduceeAction.getRemote(type); IntroduceeProtocolState nextState = currentState.next(action); @@ -162,13 +156,12 @@ public class IntroduceeEngine } // update local session state with next protocol state - localState.put(STATE, nextState.getValue()); + localState.setState(nextState); List messages; List events; // we received the introduction request if (currentState == AWAIT_REQUEST) { - // remember the session ID used by the introducer - localState.put(SESSION_ID, msg.getRaw(SESSION_ID)); + localState.setSessionId(new SessionId(msg.getRaw(SESSION_ID))); addRequestData(localState, msg); messages = Collections.emptyList(); @@ -178,29 +171,33 @@ public class IntroduceeEngine // we had sent our response already and now received the other one else if (currentState == AWAIT_RESPONSES || currentState == AWAIT_REMOTE_RESPONSE) { + // update next state based on message content action = IntroduceeAction .getRemote(type, msg.getBoolean(ACCEPT)); nextState = currentState.next(action); - localState.put(STATE, nextState.getValue()); + localState.setState(nextState); addResponseData(localState, msg); if (nextState == AWAIT_ACK) { - localState.put(TASK, TASK_ADD_CONTACT); + localState.setTask(TASK_ADD_CONTACT); +// messages = Collections +// .singletonList(getAckMessage(localState)); } messages = Collections.emptyList(); events = Collections.emptyList(); + } // we already sent our ACK and now received the other one else if (currentState == AWAIT_ACK) { - localState.put(TASK, TASK_ACTIVATE_CONTACT); + localState.setTask(TASK_ACTIVATE_CONTACT); addAckData(localState, msg); messages = Collections.emptyList(); events = Collections.emptyList(); } // we are done (probably declined response), ignore & delete message else if (currentState == FINISHED) { - return new StateUpdate(true, + return new StateUpdate(true, false, localState, Collections.emptyList(), Collections.emptyList()); @@ -209,62 +206,58 @@ public class IntroduceeEngine else { throw new IllegalArgumentException(); } - return new StateUpdate(false, false, - localState, messages, events); + return new StateUpdate(false, + false, localState, messages, events); } catch (FormatException e) { throw new IllegalArgumentException(e); } } - private void addRequestData(BdfDictionary localState, BdfDictionary msg) - throws FormatException { + private void addRequestData(IntroduceeSessionState localState, + BdfDictionary msg) throws FormatException { - localState.put(NAME, msg.getString(NAME)); - localState.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY)); + localState.setName(msg.getString(NAME)); + localState.setIntroducedPublicKey(msg.getRaw(PUBLIC_KEY)); if (msg.containsKey(MSG)) { - localState.put(MSG, msg.getString(MSG)); + localState.setMessage(msg.getString(MSG)); } } - private void addResponseData(BdfDictionary localState, BdfDictionary msg) - throws FormatException { + private void addResponseData(IntroduceeSessionState localState, + BdfDictionary msg) throws FormatException { - if (localState.containsKey(ACCEPT)) { - localState.put(ACCEPT, - localState.getBoolean(ACCEPT) && msg.getBoolean(ACCEPT)); - } else { - localState.put(ACCEPT, msg.getBoolean(ACCEPT)); - } - localState.put(NOT_OUR_RESPONSE, msg.getRaw(MESSAGE_ID)); + localState.setAccept(msg.getBoolean(ACCEPT)); + localState.setOtherResponseId(msg.getRaw(MESSAGE_ID)); if (msg.getBoolean(ACCEPT)) { - localState.put(TIME, msg.getLong(TIME)); - localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY)); - localState.put(TRANSPORT, msg.getDictionary(TRANSPORT)); + localState.setTheirTime(msg.getLong(TIME)); + localState.setEPublicKey(msg.getRaw(E_PUBLIC_KEY)); + localState.setTransport(msg.getDictionary(TRANSPORT)); } } - private void addAckData(BdfDictionary localState, BdfDictionary msg) + private void addAckData(IntroduceeSessionState localState, BdfDictionary msg) throws FormatException { - localState.put(MAC, msg.getRaw(MAC)); - localState.put(SIGNATURE, msg.getRaw(SIGNATURE)); + localState.setMac(msg.getRaw(MAC)); + localState.setSignature(msg.getRaw(SIGNATURE)); } - private BdfDictionary getAckMessage(BdfDictionary localState) + private BdfDictionary getAckMessage(IntroduceeSessionState localState) throws FormatException { BdfDictionary m = new BdfDictionary(); m.put(TYPE, TYPE_ACK); - m.put(GROUP_ID, localState.getRaw(GROUP_ID)); - m.put(SESSION_ID, localState.getRaw(SESSION_ID)); - m.put(MAC, localState.getRaw(OUR_MAC)); - m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE)); + m.put(MAC, localState.getOurMac()); + m.put(SIGNATURE, localState.getOurSignature()); + m.put(GROUP_ID, localState.getIntroductionGroupId()); + m.put(SESSION_ID, localState.getSessionId()); if (LOG.isLoggable(INFO)) { LOG.info("Sending ACK " + " to " + - localState.getString(INTRODUCER) + " for " + - localState.getString(NAME) + " with session ID " + + localState.getIntroducerName() + " for " + + localState.getName() + + " with session ID " + Arrays.hashCode(m.getRaw(SESSION_ID)) + " in group " + Arrays.hashCode(m.getRaw(GROUP_ID))); } @@ -272,20 +265,21 @@ public class IntroduceeEngine } private void logAction(IntroduceeProtocolState state, - BdfDictionary localState, BdfDictionary msg) { + IntroduceeSessionState localState, BdfDictionary msg) { if (!LOG.isLoggable(INFO)) return; try { LOG.info("Sending " + - (localState.getBoolean(ACCEPT) ? "accept " : "decline ") + + (localState.getAccept() ? "accept " : "decline ") + "response in state " + state.name() + - " to " + localState.getString(INTRODUCER) + - " for " + localState.getString(NAME) + " with session ID " + + " to " + localState.getName() + + " for " + localState.getIntroducerName() + + " with session ID " + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + "Moving on to state " + - getState(localState.getLong(STATE)).name() + localState.getState().name() ); } catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -293,8 +287,8 @@ public class IntroduceeEngine } private void logMessageReceived(IntroduceeProtocolState currentState, - IntroduceeProtocolState nextState, BdfDictionary localState, - int type, BdfDictionary msg) { + IntroduceeProtocolState nextState, + IntroduceeSessionState localState, int type, BdfDictionary msg) { if (!LOG.isLoggable(INFO)) return; @@ -306,9 +300,9 @@ public class IntroduceeEngine else if (type == TYPE_ABORT) t = "Abort"; LOG.info("Received " + t + " in state " + currentState.name() + - " from " + localState.getString(INTRODUCER) + - (localState.containsKey(NAME) ? - " related to " + localState.getString(NAME) : "") + + " from " + localState.getIntroducerName() + + (localState.getName() != null ? + " related to " + localState.getName() : "") + " with session ID " + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + @@ -320,8 +314,8 @@ public class IntroduceeEngine } @Override - public StateUpdate onMessageDelivered( - BdfDictionary localState, BdfDictionary delivered) { + public StateUpdate onMessageDelivered( + IntroduceeSessionState localState, BdfDictionary delivered) { try { return noUpdate(localState); } catch (FormatException e) { @@ -330,25 +324,18 @@ public class IntroduceeEngine } } - private IntroduceeProtocolState getState(Long state) { - return IntroduceeProtocolState.fromValue(state.intValue()); - } - - private Event getEvent(BdfDictionary localState, BdfDictionary msg) + private Event getEvent(IntroduceeSessionState localState, BdfDictionary msg) throws FormatException { - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - AuthorId authorId = new AuthorId(localState.getRaw(REMOTE_AUTHOR_ID)); - - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + ContactId contactId = localState.getIntroducerId(); + AuthorId authorId = localState.getRemoteAuthorId(); + SessionId sessionId = localState.getSessionId(); MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); long time = msg.getLong(MESSAGE_TIME); String name = msg.getString(NAME); String message = msg.getOptionalString(MSG); - boolean exists = localState.getBoolean(EXISTS); - boolean introducesOtherIdentity = - localState.getBoolean(REMOTE_AUTHOR_IS_US); + boolean exists = localState.getContactExists(); + boolean introducesOtherIdentity = localState.getRemoteAuthorIsUs(); IntroductionRequest ir = new IntroductionRequest(sessionId, messageId, ROLE_INTRODUCEE, time, false, false, false, false, authorId, @@ -356,39 +343,39 @@ public class IntroduceeEngine return new IntroductionRequestReceivedEvent(contactId, ir); } - private StateUpdate abortSession( - IntroduceeProtocolState currentState, BdfDictionary localState) - throws FormatException { + private StateUpdate abortSession( + IntroduceeProtocolState currentState, + IntroduceeSessionState localState) throws FormatException { if (LOG.isLoggable(WARNING)) { LOG.warning("Aborting protocol session " + - Arrays.hashCode(localState.getRaw(SESSION_ID)) + - " in state " + currentState.name()); + Arrays.hashCode(localState.getSessionId().getBytes()) + + " in state " + currentState.name() + ); } - localState.put(STATE, ERROR.getValue()); - localState.put(TASK, TASK_ABORT); + localState.setState(ERROR); + localState.setTask(TASK_ABORT); BdfDictionary msg = new BdfDictionary(); msg.put(TYPE, TYPE_ABORT); - msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); - msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getIntroductionGroupId()); + msg.put(SESSION_ID, localState.getSessionId()); List messages = Collections.singletonList(msg); // send abort event - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + ContactId contactId = localState.getIntroducerId(); + SessionId sessionId = localState.getSessionId(); Event event = new IntroductionAbortedEvent(contactId, sessionId); List events = Collections.singletonList(event); - return new StateUpdate(false, false, - localState, messages, events); + return new StateUpdate(false, + false, localState, messages, events); } - private StateUpdate noUpdate( - BdfDictionary localState) throws FormatException { + private StateUpdate noUpdate( + IntroduceeSessionState localState) throws FormatException { - return new StateUpdate(false, false, + return new StateUpdate(false, false, localState, Collections.emptyList(), Collections.emptyList()); } diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java index c5df31c10..a1378fb08 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java @@ -4,6 +4,7 @@ import org.briarproject.api.Bytes; import org.briarproject.api.FormatException; import org.briarproject.api.TransportId; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; @@ -46,9 +47,6 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; -import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.api.introduction.IntroductionConstants.ADDED_CONTACT_ID; -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; @@ -78,10 +76,14 @@ import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE; import static org.briarproject.api.introduction.IntroductionConstants.STATE; import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; import static org.briarproject.api.introduction.IntroductionConstants.TASK; +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.NO_TASK; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; import static org.briarproject.api.introduction.IntroductionConstants.TASK_ABORT; import static org.briarproject.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT; import static org.briarproject.api.introduction.IntroductionConstants.TASK_ADD_CONTACT; -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; @@ -125,8 +127,9 @@ class IntroduceeManager { this.introductionGroupFactory = introductionGroupFactory; } - public BdfDictionary initialize(Transaction txn, GroupId groupId, - BdfDictionary message) throws DbException, FormatException { + public IntroduceeSessionState initialize(Transaction txn, + SessionId sessionId, GroupId groupId, BdfDictionary message) + throws DbException, FormatException { // create local message to keep engine state long now = clock.currentTimeMillis(); @@ -145,16 +148,10 @@ class IntroduceeManager { new ContactId(gd.getLong(CONTACT).intValue()); Contact introducer = db.getContact(txn, introducerId); - BdfDictionary d = new BdfDictionary(); - d.put(STORAGE_ID, storageId); - d.put(STATE, AWAIT_REQUEST.getValue()); - d.put(ROLE, ROLE_INTRODUCEE); - d.put(GROUP_ID, groupId); - 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, storageId); - d.put(ANSWERED, false); + IntroduceeSessionState localState = new IntroduceeSessionState(storageId, + sessionId, groupId, introducer.getId(), + introducer.getAuthor().getId(), introducer.getAuthor().getName(), + introducer.getLocalAuthorId(), AWAIT_REQUEST); // check if the contact we are introduced to does already exist AuthorId remoteAuthorId = authorFactory @@ -162,8 +159,10 @@ class IntroduceeManager { message.getRaw(PUBLIC_KEY)).getId(); boolean exists = contactManager.contactExists(txn, remoteAuthorId, introducer.getLocalAuthorId()); - d.put(EXISTS, exists); - d.put(REMOTE_AUTHOR_ID, remoteAuthorId); + localState.setContactExists(exists); + localState.setRemoteAuthorId(remoteAuthorId); + localState.setLocalAuthorId((introducer.getLocalAuthorId())); + localState.setName(message.getString(NAME)); // check if someone is trying to introduce us to ourselves if(remoteAuthorId.equals(introducer.getLocalAuthorId())) { @@ -174,40 +173,39 @@ class IntroduceeManager { // check if remote author is actually one of our other identities boolean introducesOtherIdentity = db.containsLocalAuthor(txn, remoteAuthorId); - d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity); + localState.setRemoteAuthorIsUs(introducesOtherIdentity); // save local state to database - clientHelper.addLocalMessage(txn, localMsg, d, false); + clientHelper.addLocalMessage(txn, localMsg, + localState.toBdfDictionary(), false); - return d; + return localState; } - public void incomingMessage(Transaction txn, BdfDictionary state, + public void incomingMessage(Transaction txn, IntroduceeSessionState state, BdfDictionary message) throws DbException, FormatException { IntroduceeEngine engine = new IntroduceeEngine(); processStateUpdate(txn, message, engine.onMessageReceived(state, message)); } - public void acceptIntroduction(Transaction txn, BdfDictionary state, - final long timestamp) + void acceptIntroduction(Transaction txn, + IntroduceeSessionState state, final long timestamp) throws DbException, FormatException { // get data to connect and derive a shared secret later long now = clock.currentTimeMillis(); KeyPair keyPair = cryptoComponent.generateAgreementKeyPair(); - byte[] publicKey = keyPair.getPublic().getEncoded(); - byte[] privateKey = keyPair.getPrivate().getEncoded(); Map transportProperties = transportPropertyManager.getLocalProperties(txn); BdfDictionary tp = encodeTransportProperties(transportProperties); // update session state for later - state.put(ACCEPT, true); - state.put(OUR_TIME, now); - state.put(OUR_PUBLIC_KEY, publicKey); - state.put(OUR_PRIVATE_KEY, privateKey); - state.put(OUR_TRANSPORT, tp); + state.setAccept(true); + state.setOurTime(now); + state.setOurPrivateKey(keyPair.getPrivate().getEncoded()); + state.setOurPublicKey(keyPair.getPublic().getEncoded()); + state.setOurTransport(tp); // define action BdfDictionary localAction = new BdfDictionary(); @@ -220,12 +218,12 @@ class IntroduceeManager { processStateUpdate(txn, null, engine.onLocalAction(state, localAction)); } - public void declineIntroduction(Transaction txn, BdfDictionary state, - final long timestamp) + void declineIntroduction(Transaction txn, + IntroduceeSessionState state, final long timestamp) throws DbException, FormatException { // update session state - state.put(ACCEPT, false); + state.setAccept(false); // define action BdfDictionary localAction = new BdfDictionary(); @@ -239,16 +237,16 @@ class IntroduceeManager { } private void processStateUpdate(Transaction txn, BdfDictionary msg, - IntroduceeEngine.StateUpdate + IntroduceeEngine.StateUpdate result) throws DbException, FormatException { // perform actions based on new local state performTasks(txn, result.localState); // save new local state - MessageId storageId = - new MessageId(result.localState.getRaw(STORAGE_ID)); - clientHelper.mergeMessageMetadata(txn, storageId, result.localState); + MessageId storageId = result.localState.getStorageId(); + clientHelper.mergeMessageMetadata(txn, storageId, + result.localState.toBdfDictionary()); // send messages for (BdfDictionary d : result.toSend) { @@ -271,18 +269,18 @@ class IntroduceeManager { } } - private void performTasks(Transaction txn, BdfDictionary localState) + private void performTasks(Transaction txn, + IntroduceeSessionState localState) throws FormatException, DbException { - if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE) - return; + long task = localState.getTask(); + if (task == NO_TASK) return; // remember task and remove it from localState - long task = localState.getLong(TASK); - localState.put(TASK, NULL_VALUE); + localState.setTask(NO_TASK); if (task == TASK_ADD_CONTACT) { - if (localState.getBoolean(EXISTS)) { + if (localState.getContactExists()) { // we have this contact already, so do not perform actions LOG.info("We have this contact already, do not add"); return; @@ -296,11 +294,10 @@ class IntroduceeManager { PublicKey publicKey; PrivateKey privateKey; try { - publicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY); - publicKey = keyParser - .parsePublicKey(publicKeyBytes); + publicKeyBytes = localState.getOurPublicKey(); + publicKey = keyParser.parsePublicKey(publicKeyBytes); privateKey = keyParser.parsePrivateKey( - localState.getRaw(OUR_PRIVATE_KEY)); + localState.getOurPrivateKey()); } catch (GeneralSecurityException e) { if (LOG.isLoggable(WARNING)) { LOG.log(WARNING, e.toString(), e); @@ -308,11 +305,13 @@ class IntroduceeManager { // we can not continue without the keys throw new RuntimeException("Our own ephemeral key is invalid"); } - KeyPair keyPair = new KeyPair(publicKey, privateKey); - byte[] theirEphemeralKey = localState.getRaw(E_PUBLIC_KEY); + + KeyPair ourEphemeralKeyPair; + ourEphemeralKeyPair = new KeyPair(publicKey, privateKey); + byte[] theirEphemeralKey = localState.getEPublicKey(); // figure out who takes which role by comparing public keys - int comp = Bytes.COMPARATOR.compare(new Bytes(publicKeyBytes), + int comp = new Bytes(publicKeyBytes).compareTo( new Bytes(theirEphemeralKey)); boolean alice = comp < 0; @@ -321,7 +320,8 @@ class IntroduceeManager { SecretKey secretKey; try { secretKey = cryptoComponent - .deriveMasterSecret(theirEphemeralKey, keyPair, alice); + .deriveMasterSecret(theirEphemeralKey, + ourEphemeralKeyPair, alice); } catch (GeneralSecurityException e) { // we can not continue without the shared secret throw new DbException(e); @@ -337,12 +337,11 @@ class IntroduceeManager { cryptoComponent.deriveMacKey(secretKey, !alice); // Save the other nonce and MAC key for the verification - localState.put(NONCE, theirNonce); - localState.put(MAC_KEY, theirMacKey.getBytes()); + localState.setNonce(theirNonce); + localState.setMacKey(theirMacKey.getBytes()); // Sign our nonce with our long-term identity public key - AuthorId localAuthorId = - new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID)); + AuthorId localAuthorId = localState.getLocalAuthorId(); LocalAuthor author = identityManager.getLocalAuthor(txn, localAuthorId); Signature signature = cryptoComponent.getSignature(); @@ -358,44 +357,45 @@ class IntroduceeManager { signature.update(ourNonce); byte[] sig = signature.sign(); + // The agreed timestamp is the minimum of the peers' timestamps - long ourTime = localState.getLong(OUR_TIME); - long theirTime = localState.getLong(TIME); + long ourTime = localState.getOurTime(); + long theirTime = localState.getTheirTime(); long timestamp = Math.min(ourTime, theirTime); // Calculate a MAC over identity public key, ephemeral public key, // transport properties and timestamp. - BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT); + BdfDictionary tp = localState.getOurTransport(); BdfList toSignList = BdfList.of(author.getPublicKey(), publicKeyBytes, tp, ourTime); byte[] toSign = clientHelper.toByteArray(toSignList); byte[] mac = cryptoComponent.mac(macKey, toSign); // Add MAC and signature to localState, so it can be included in ACK - localState.put(OUR_MAC, mac); - localState.put(OUR_SIGNATURE, sig); + localState.setOurMac(mac); + localState.setOurSignature(sig); // Add the contact to the database as inactive Author remoteAuthor = authorFactory - .createAuthor(localState.getString(NAME), - localState.getRaw(PUBLIC_KEY)); + .createAuthor(localState.getName(), + localState.getIntroducedPublicKey()); ContactId contactId = contactManager .addContact(txn, remoteAuthor, localAuthorId, secretKey, timestamp, alice, false, false); // Update local state with ContactId, so we know what to activate - localState.put(ADDED_CONTACT_ID, contactId.getInt()); + localState.setIntroducedId(contactId); // let the transport manager know how to connect to the contact Map transportProperties = - parseTransportProperties(localState); + parseTransportProperties(localState.toBdfDictionary()); transportPropertyManager.addRemoteProperties(txn, contactId, transportProperties); // delete the ephemeral private key by overwriting with NULL value // this ensures future ephemeral keys can not be recovered when // this device should gets compromised - localState.put(OUR_PRIVATE_KEY, NULL_VALUE); + localState.clearOurKeyPair(); // define next action: Send ACK BdfDictionary localAction = new BdfDictionary(); @@ -409,14 +409,13 @@ class IntroduceeManager { // we sent and received an ACK, so activate contact if (task == TASK_ACTIVATE_CONTACT) { - if (!localState.getBoolean(EXISTS) && - localState.containsKey(ADDED_CONTACT_ID)) { + if (!localState.getContactExists() && localState.getIntroducedId() != null) { LOG.info("Verifying Signature..."); - byte[] nonce = localState.getRaw(NONCE); - byte[] sig = localState.getRaw(SIGNATURE); - byte[] keyBytes = localState.getRaw(PUBLIC_KEY); + byte[] nonce = localState.getNonce(); + byte[] sig = localState.getSignature(); + byte[] keyBytes = localState.getIntroducedPublicKey(); try { // Parse the public key KeyParser keyParser = cryptoComponent.getSignatureKeyParser(); @@ -439,15 +438,15 @@ class IntroduceeManager { LOG.info("Verifying MAC..."); // get MAC and MAC key from session state - byte[] mac = localState.getRaw(MAC); - byte[] macKeyBytes = localState.getRaw(MAC_KEY); + byte[] mac = localState.getMac(); + byte[] macKeyBytes = localState.getMacKey(); SecretKey macKey = new SecretKey(macKeyBytes); // get MAC data and calculate a new MAC with stored key - byte[] pubKey = localState.getRaw(PUBLIC_KEY); - byte[] ePubKey = localState.getRaw(E_PUBLIC_KEY); - BdfDictionary tp = localState.getDictionary(TRANSPORT); - long timestamp = localState.getLong(TIME); + byte[] pubKey = localState.getIntroducedPublicKey(); + byte[] ePubKey = localState.getEPublicKey(); + BdfDictionary tp = localState.getTransport(); + long timestamp = localState.getTheirTime(); BdfList toSignList = BdfList.of(pubKey, ePubKey, tp, timestamp); byte[] toSign = clientHelper.toByteArray(toSignList); byte[] calculatedMac = cryptoComponent.mac(macKey, toSign); @@ -458,8 +457,7 @@ class IntroduceeManager { LOG.info("Activating Contact..."); - ContactId contactId = new ContactId( - localState.getLong(ADDED_CONTACT_ID).intValue()); + ContactId contactId = localState.getIntroducedId(); // activate and show contact in contact list contactManager.setContactActive(txn, contactId, true); @@ -476,17 +474,16 @@ class IntroduceeManager { // we need to abort the protocol, clean up what has been done if (task == TASK_ABORT) { - if (localState.containsKey(ADDED_CONTACT_ID)) { + if (localState.getIntroducedId() != null) { LOG.info("Deleting added contact due to abort..."); - ContactId contactId = new ContactId( - localState.getLong(ADDED_CONTACT_ID).intValue()); + ContactId contactId = localState.getIntroducedId(); contactManager.removeContact(txn, contactId); } } } - public void abort(Transaction txn, BdfDictionary state) { + public void abort(Transaction txn, IntroduceeSessionState state) { IntroduceeEngine engine = new IntroduceeEngine(); BdfDictionary localAction = new BdfDictionary(); diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeSessionState.java b/briar-core/src/org/briarproject/introduction/IntroduceeSessionState.java new file mode 100644 index 000000000..4906aa5df --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroduceeSessionState.java @@ -0,0 +1,584 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroduceeProtocolState; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.ADDED_CONTACT_ID; +import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED; +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.E_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.EXISTS; +import static org.briarproject.api.introduction.IntroductionConstants.MSG; +import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE; +import static org.briarproject.api.introduction.IntroductionConstants.MAC; +import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; +import static org.briarproject.api.introduction.IntroductionConstants.TASK; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1; +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.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.NONCE; +import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; +import static org.briarproject.api.introduction.IntroductionConstants.NO_TASK; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME; +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.OUR_TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC; +import static org.briarproject.api.introduction.IntroductionConstants.MAC_KEY; + + +class IntroduceeSessionState extends IntroductionState { + + private IntroduceeProtocolState state; + + private final ContactId introducerId; + private final AuthorId introducerAuthorId; + private final String introducerName; + + private AuthorId localAuthorId; + + private long ourTime; + private long theirTime; + + private byte[] ourPrivateKey; + private byte[] ourPublicKey; + private byte[] introducedPublicKey; + private byte[] ePublicKey; + + private byte[] mac; + private byte[] signature; + private byte[] ourMac; + private byte[] ourSignature; + private BdfDictionary ourTransport; + private byte[] nonce; + private byte[] macKey; + + private int task; + + private String message; + private BdfDictionary transport; // FIXME should not be a dictionary + + private boolean answered; + private boolean accept; + private boolean accepted; + + private boolean contactAlreadyExists; + + private byte[] otherResponseId; + + private AuthorId remoteAuthorId; + private boolean remoteAuthorIsUs; + + private String name; + private GroupId introductionGroupId; + private ContactId introducedId; + private String introducedName; + private AuthorId introducedAuthorId; + + IntroduceeSessionState(MessageId storageId, SessionId sessionId, + GroupId groupId, + ContactId introducerId, AuthorId introducerAuthorId, + String introducerName, AuthorId introducerLocalAuthorId, + IntroduceeProtocolState state){ + + super(sessionId, storageId); + + this.introducerName = introducerName; + this.introducerId = introducerId; + this.introducerAuthorId = introducerAuthorId; + this.otherResponseId = sessionId.getBytes(); + this.localAuthorId = introducerLocalAuthorId; + this.state = state; + this.answered = false; + this.accept = false; + this.accepted = false; + this.contactAlreadyExists= false; + this.otherResponseId = null; + this.task = NO_TASK; + this.transport = null; + this.introductionGroupId = groupId; + + // these are not set during initialization, so we default them to null + this.introducedName = null; + this.introducedAuthorId = null; + this.introducedId = null; + this.introducedPublicKey = null; + this.ourPublicKey = null; + this.ourPrivateKey = null; + this.ePublicKey = null; + this.introducedPublicKey = null; + this.message = null; + this.mac = null; + this.signature = null; + this.ourMac = null; + this.ourSignature = null; + this.ourTransport = null; + this.nonce = null; + this.macKey = null; + } + + public BdfDictionary toBdfDictionary() { + BdfDictionary d = super.toBdfDictionary(); + d.put(ROLE, ROLE_INTRODUCEE); + d.put(STATE, getState().getValue()); + d.put(INTRODUCER, introducerName); + d.put(ANSWERED, answered); + d.put(REMOTE_AUTHOR_IS_US, remoteAuthorIsUs); + + if (message != null) + d.put(MSG, message); + + if (accepted) + d.put(ACCEPT, accept); + + if (introducedId != null) + d.put(ADDED_CONTACT_ID, introducedId.getInt()); + + d.put(GROUP_ID_1, introductionGroupId); + d.put(GROUP_ID, introductionGroupId); + + d.put(AUTHOR_ID_1, introducerAuthorId); + d.put(CONTACT_1, introducerName); + d.put(CONTACT_ID_1, introducerId.getInt()); + + if (introducedAuthorId != null) { + d.put(AUTHOR_ID_2, introducedAuthorId); + d.put(CONTACT_2, introducedName); + d.put(CONTACT_ID_2, introducedId); + } + // TODO check if we really need three names and what this name refers to + if (name != null) d.put(NAME, name); + + if (remoteAuthorId != null) + d.put(REMOTE_AUTHOR_ID, remoteAuthorId); + + d.put(LOCAL_AUTHOR_ID, localAuthorId); + + if (transport != null) + d.put(TRANSPORT, transport); + + if (ourPublicKey != null) + d.put(OUR_PUBLIC_KEY, ourPublicKey); + + if (ourPrivateKey != null) + d.put(OUR_PRIVATE_KEY, ourPrivateKey); + + if (ePublicKey != null) + d.put(E_PUBLIC_KEY, ePublicKey); + + if (introducedPublicKey != null) + d.put(PUBLIC_KEY, introducedPublicKey); + + if (otherResponseId != null) + d.put(NOT_OUR_RESPONSE, getOtherResponseId()); + + if (mac != null) + d.put(MAC, mac); + + if (signature != null) + d.put(SIGNATURE, signature); + + if (ourMac != null) + d.put(OUR_MAC, ourMac); + + if (ourSignature != null) + d.put(OUR_SIGNATURE, ourSignature); + + if (ourTransport != null) + d.put(OUR_TRANSPORT, ourTransport); + + if (nonce != null) + d.put(NONCE, nonce); + + if (macKey != null) { + d.put(MAC_KEY, macKey); + } + + d.put(TIME, theirTime); + d.put(OUR_TIME, ourTime); + d.put(EXISTS, contactAlreadyExists); + d.put(TASK, task); + + return d; + } + + public static IntroduceeSessionState fromBdfDictionary(BdfDictionary d) + throws FormatException{ + + if (d.getLong(ROLE).intValue() != ROLE_INTRODUCEE) + throw new FormatException(); + + MessageId storageId = new MessageId(d.getRaw(STORAGE_ID)); + SessionId sessionId = new SessionId(d.getRaw(SESSION_ID)); + + // FIXME: do we need both GROUP_ID and GROUP_ID_1? + GroupId groupId = new GroupId(d.getRaw(GROUP_ID_1)); + + AuthorId iaid = new AuthorId(d.getRaw(AUTHOR_ID_1)); + String iname = d.getString(INTRODUCER); + ContactId iid = new ContactId(d.getLong(CONTACT_ID_1).intValue()); + AuthorId liaid = new AuthorId(d.getRaw(LOCAL_AUTHOR_ID)); + + int stateno = d.getLong(STATE).intValue(); + IntroduceeProtocolState state = + IntroduceeProtocolState.fromValue(stateno); + + IntroduceeSessionState sessionState = new IntroduceeSessionState(storageId, + sessionId, groupId, iid, iaid, iname, liaid, state); + + if (d.containsKey(CONTACT_2)) { + sessionState.setIntroducedName(d.getString(CONTACT_2)); + sessionState + .setIntroducedAuthorId(new AuthorId(d.getRaw(AUTHOR_ID_2))); + sessionState.setIntroducedId( + new ContactId(d.getLong(CONTACT_ID_2).intValue())); + } + + if (d.containsKey(REMOTE_AUTHOR_ID)) + sessionState.setRemoteAuthorId(new AuthorId(d.getRaw(REMOTE_AUTHOR_ID))); + + if (d.containsKey(TRANSPORT)) + sessionState.setTransport(d.getDictionary(TRANSPORT)); + + if (d.containsKey(OUR_PUBLIC_KEY)) + sessionState.ourPublicKey = d.getRaw(OUR_PUBLIC_KEY); + + if (d.containsKey(OUR_PRIVATE_KEY)) + sessionState.ourPrivateKey = d.getRaw(OUR_PRIVATE_KEY); + + if (d.containsKey(E_PUBLIC_KEY)) + sessionState.ePublicKey = d.getRaw(E_PUBLIC_KEY); + + if (d.containsKey(PUBLIC_KEY)) + sessionState.setIntroducedPublicKey(d.getRaw(PUBLIC_KEY)); + + if (d.containsKey(ACCEPT)) + sessionState.setAccept(d.getBoolean(ACCEPT)); + + if (d.containsKey(NOT_OUR_RESPONSE)) + sessionState.setOtherResponseId(d.getRaw(NOT_OUR_RESPONSE)); + + if (d.containsKey(MAC)) + sessionState.setMac(d.getRaw(MAC)); + + if (d.containsKey(SIGNATURE)) + sessionState.setSignature(d.getRaw(SIGNATURE)); + + if (d.containsKey(OUR_TRANSPORT)) + sessionState.setOurTransport(d.getDictionary(OUR_TRANSPORT)); + + sessionState.setTheirTime(d.getLong(TIME)); + sessionState.setOurTime(d.getLong(OUR_TIME)); + sessionState.setName(d.getString(NAME)); + sessionState.setContactExists(d.getBoolean(EXISTS)); + sessionState.setTask(d.getLong(TASK).intValue()); + sessionState.setRemoteAuthorIsUs(d.getBoolean(REMOTE_AUTHOR_IS_US)); + + if (d.containsKey(ADDED_CONTACT_ID)) { + ContactId introducedId = + new ContactId(d.getLong(ADDED_CONTACT_ID).intValue()); + sessionState.setIntroducedId(introducedId); + } + + if (d.containsKey(ANSWERED)) + sessionState.setAnswered(d.getBoolean(ANSWERED)); + + if (d.containsKey(MSG)) + sessionState.setMessage(d.getString(MSG)); + + if (d.containsKey(NONCE)) + sessionState.setNonce(d.getRaw(NONCE)); + + if (d.containsKey(MAC_KEY)) + sessionState.setMacKey(d.getRaw(MAC_KEY)); + + if (d.containsKey(OUR_MAC)) { + sessionState.setOurMac(d.getRaw(OUR_MAC)); + } + + if (d.containsKey(OUR_SIGNATURE)) + sessionState.setOurSignature(d.getRaw(OUR_SIGNATURE)); + + + return sessionState; + } + + + public IntroduceeProtocolState getState() { + return state; + } + + public void setState(IntroduceeProtocolState state) { + this.state = state; + } + + public boolean getAccept() { + return accept; + } + + public void setAccept(boolean accept) { + if (accepted) { + this.accept &= accept; + } else { + this.accept = accept; + } + accepted = true; + } + + void setAnswered(boolean answered) { + this.answered = answered; + } + + boolean getAnswered() { + return answered; + } + + long getOurTime() { + return ourTime; + } + + void setOurTime(long time) { + ourTime = time; + } + + long getTheirTime() { + return theirTime; + } + + public void setTheirTime(long time) { + theirTime = time; + } + + public void setMessage(String message) { + this.message = message; + } + + byte[] getEPublicKey() { + return ePublicKey; + } + + void setEPublicKey(byte[] ePublicKey) { + this.ePublicKey = ePublicKey; + } + + public void setTransport(BdfDictionary transport) { + this.transport = transport; + } + + boolean getContactExists() { + return contactAlreadyExists; + } + + void setContactExists(boolean exists) { + this.contactAlreadyExists = exists; + } + + void setRemoteAuthorId(AuthorId remoteAuthorId) { + this.remoteAuthorId = remoteAuthorId; + } + + AuthorId getRemoteAuthorId() { + return remoteAuthorId; + } + + void setRemoteAuthorIsUs(boolean remoteAuthorIsUs) { + this.remoteAuthorIsUs = remoteAuthorIsUs; + } + + boolean getRemoteAuthorIsUs() { + return remoteAuthorIsUs; + } + + void clearOurKeyPair() { + this.ourPrivateKey = null; + this.ourPublicKey = null; + } + + byte[] getOtherResponseId() { + return this.otherResponseId; + } + + void setOtherResponseId(byte[] otherResponse) { + this.otherResponseId = otherResponse; + } + + public String getMessage() { + return message; + } + + public void setTask(int task) { + this.task = task; + } + + public int getTask() { + return task; + } + + boolean wasAccepted() { + return accepted; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public String getIntroducerName() { + return introducerName; + } + + public byte[] getOurPublicKey() { + return ourPublicKey; + } + + public void setOurPublicKey(byte[] ourPublicKey) { + this.ourPublicKey = ourPublicKey; + } + + public byte[] getOurPrivateKey() { + return ourPrivateKey; + } + + public void setOurPrivateKey(byte[] ourPrivateKey) { + this.ourPrivateKey = ourPrivateKey; + } + + public GroupId getIntroductionGroupId() { + return introductionGroupId; + } + + public void setIntroductionGroupId( + GroupId introductionGroupId) { + this.introductionGroupId = introductionGroupId; + } + + public byte[] getIntroducedPublicKey() { + return introducedPublicKey; + } + + public void setIntroducedPublicKey(byte[] introducedPublicKey) { + this.introducedPublicKey = introducedPublicKey; + } + + public String getIntroducedName() { + return introducedName; + } + + public void setIntroducedName(String introducedName) { + this.introducedName = introducedName; + } + + public ContactId getIntroducedId() { + return introducedId; + } + + public void setIntroducedId( + ContactId introducedId) { + this.introducedId = introducedId; + } + + public void setIntroducedAuthorId( + AuthorId introducedAuthorId) { + this.introducedAuthorId = introducedAuthorId; + } + + public AuthorId getLocalAuthorId() { + return localAuthorId; + } + + public void setLocalAuthorId( + AuthorId localAuthorId) { + this.localAuthorId = localAuthorId; + } + + public ContactId getIntroducerId() { + return introducerId; + } + + public void setMac(byte[] mac) { + this.mac = mac; + } + + public byte[] getMac() { + return mac; + } + + public void setSignature(byte[] signature) { + this.signature = signature; + } + + public byte[] getSignature() { + return signature; + } + + public void setOurMac(byte[] ourMac) { + this.ourMac = ourMac; + } + + public byte[] getOurMac() { + return ourMac; + } + + public void setOurSignature(byte[] ourSignature) { + this.ourSignature = ourSignature; + } + + public byte[] getOurSignature() { + return ourSignature; + } + + public void setOurTransport(BdfDictionary ourTransport) { + this.ourTransport = ourTransport; + } + + public BdfDictionary getOurTransport() { + return ourTransport; + } + + public void setNonce(byte[] nonce) { + this.nonce = nonce; + } + + public byte[] getNonce() { + return nonce; + } + + public void setMacKey(byte[] macKey) { + this.macKey = macKey; + } + + public byte[] getMacKey() { + return this.macKey; + } + + public BdfDictionary getTransport() { + return transport; + } +} diff --git a/briar-core/src/org/briarproject/introduction/IntroducerEngine.java b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java index a431fd7e4..33ecf7cd9 100644 --- a/briar-core/src/org/briarproject/introduction/IntroducerEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java @@ -13,6 +13,7 @@ import org.briarproject.api.introduction.IntroducerProtocolState; import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.clients.SessionId; import org.briarproject.api.sync.MessageId; +import org.briarproject.introduction.IntroducerSessionState; import java.util.ArrayList; import java.util.Arrays; @@ -65,18 +66,18 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; public class IntroducerEngine - implements ProtocolEngine { + implements ProtocolEngine { private static final Logger LOG = Logger.getLogger(IntroducerEngine.class.getName()); + @Override - public StateUpdate onLocalAction( - BdfDictionary localState, BdfDictionary localAction) { + public StateUpdate onLocalAction( + IntroducerSessionState localState, BdfDictionary localAction) { try { - IntroducerProtocolState currentState = - getState(localState.getLong(STATE)); + IntroducerProtocolState currentState = localState.getState(); int type = localAction.getLong(TYPE).intValue(); IntroducerAction action = IntroducerAction.getLocal(type); IntroducerProtocolState nextState = currentState.next(action); @@ -93,15 +94,15 @@ public class IntroducerEngine return noUpdate(localState); } - localState.put(STATE, nextState.getValue()); + localState.setState(nextState); if (action == LOCAL_REQUEST) { // create the introduction requests for both contacts List messages = new ArrayList(2); BdfDictionary msg1 = new BdfDictionary(); msg1.put(TYPE, TYPE_REQUEST); - msg1.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); - msg1.put(NAME, localState.getString(CONTACT_2)); + msg1.put(SESSION_ID, localState.getSessionId().getBytes()); + msg1.put(GROUP_ID, localState.getGroup1Id().getBytes()); + msg1.put(NAME, localState.getContact2Name()); msg1.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY2)); if (localAction.containsKey(MSG)) { msg1.put(MSG, localAction.getString(MSG)); @@ -111,9 +112,9 @@ public class IntroducerEngine logLocalAction(currentState, localState, msg1); BdfDictionary msg2 = new BdfDictionary(); msg2.put(TYPE, TYPE_REQUEST); - msg2.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); - msg2.put(NAME, localState.getString(CONTACT_1)); + msg2.put(SESSION_ID, localState.getSessionId().getBytes()); + msg2.put(GROUP_ID, localState.getGroup2Id().getBytes()); + msg2.put(NAME, localState.getContact1Name()); msg2.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY1)); if (localAction.containsKey(MSG)) { msg2.put(MSG, localAction.getString(MSG)); @@ -123,8 +124,8 @@ public class IntroducerEngine logLocalAction(currentState, localState, msg2); List events = Collections.emptyList(); - return new StateUpdate(false, false, - localState, messages, events); + return new StateUpdate( + false, false,localState, messages, events); } else { throw new IllegalArgumentException("Unknown Local Action"); } @@ -134,12 +135,11 @@ public class IntroducerEngine } @Override - public StateUpdate onMessageReceived( - BdfDictionary localState, BdfDictionary msg) { + public StateUpdate onMessageReceived( + IntroducerSessionState localState, BdfDictionary msg) { try { - IntroducerProtocolState currentState = - getState(localState.getLong(STATE)); + IntroducerProtocolState currentState = localState.getState(); int type = msg.getLong(TYPE).intValue(); boolean one = isContact1(localState, msg); IntroducerAction action = IntroducerAction.getRemote(type, one); @@ -166,9 +166,11 @@ public class IntroducerEngine action = IntroducerAction .getRemote(type, one, msg.getBoolean(ACCEPT)); nextState = currentState.next(action); - localState.put(STATE, nextState.getValue()); - if (one) localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID)); - else localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID)); + localState.setState(nextState); + if (one) localState.setResponse1( + new MessageId(msg.getRaw(MESSAGE_ID))); + else localState.setResponse2( + new MessageId(msg.getRaw(MESSAGE_ID))); messages = forwardMessage(localState, msg); events = Collections.singletonList(getEvent(localState, msg)); @@ -177,7 +179,7 @@ public class IntroducerEngine else if (currentState == AWAIT_ACKS || currentState == AWAIT_ACK_1 || currentState == AWAIT_ACK_2) { - localState.put(STATE, nextState.getValue()); + localState.setState(nextState); messages = forwardMessage(localState, msg); events = Collections.emptyList(); } @@ -185,27 +187,27 @@ public class IntroducerEngine else if (currentState == FINISHED) { // if it was a response store it to be found later if (action == REMOTE_ACCEPT_1 || action == REMOTE_DECLINE_1) { - localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID)); + localState.setResponse1(new MessageId(msg.getRaw(MESSAGE_ID))); messages = Collections.emptyList(); events = Collections.singletonList(getEvent(localState, msg)); } else if (action == REMOTE_ACCEPT_2 || action == REMOTE_DECLINE_2) { - localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID)); + localState.setResponse2(new MessageId(msg.getRaw(MESSAGE_ID))); messages = Collections.emptyList(); events = Collections.singletonList(getEvent(localState, msg)); } else return noUpdate(localState); } else { throw new IllegalArgumentException("Bad state"); } - return new StateUpdate(false, false, - localState, messages, events); + return new StateUpdate(false, + false, localState, messages, events); } catch (FormatException e) { throw new IllegalArgumentException(e); } } private void logLocalAction(IntroducerProtocolState state, - BdfDictionary localState, BdfDictionary msg) { + IntroducerSessionState localState, BdfDictionary msg) { if (!LOG.isLoggable(INFO)) return; @@ -216,7 +218,7 @@ public class IntroducerEngine Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + "Moving on to state " + - getState(localState.getLong(STATE)).name() + localState.getState().name() ); } catch (FormatException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -225,7 +227,7 @@ public class IntroducerEngine private void logMessageReceived(IntroducerProtocolState currentState, IntroducerProtocolState nextState, - BdfDictionary localState, int type, BdfDictionary msg) { + IntroducerSessionState localState, int type, BdfDictionary msg) { if (!LOG.isLoggable(INFO)) return; try { @@ -249,15 +251,15 @@ public class IntroducerEngine } } - private List forwardMessage(BdfDictionary localState, + private List forwardMessage(IntroducerSessionState localState, BdfDictionary message) throws FormatException { // clone the message here, because we still need the original BdfDictionary msg = (BdfDictionary) message.clone(); if (isContact1(localState, msg)) { - msg.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); + msg.put(GROUP_ID, localState.getGroup2Id()); } else { - msg.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); + msg.put(GROUP_ID, localState.getGroup1Id()); } if (LOG.isLoggable(INFO)) { @@ -269,8 +271,8 @@ public class IntroducerEngine } @Override - public StateUpdate onMessageDelivered( - BdfDictionary localState, BdfDictionary delivered) { + public StateUpdate onMessageDelivered( + IntroducerSessionState localState, BdfDictionary delivered) { try { return noUpdate(localState); } @@ -280,23 +282,18 @@ public class IntroducerEngine } } - private IntroducerProtocolState getState(Long state) { - return IntroducerProtocolState.fromValue(state.intValue()); - } + private Event getEvent(IntroducerSessionState localState, + BdfDictionary msg) throws FormatException { - private Event getEvent(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1)); - if (Arrays.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { - contactId = - new ContactId(localState.getLong(CONTACT_ID_2).intValue()); - authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2)); + ContactId contactId = localState.getContact1Id(); + AuthorId authorId = localState.getContact1AuthorId(); + if (Arrays.equals(msg.getRaw(GROUP_ID), + localState.getGroup2Id().getBytes())) { + contactId = localState.getContact2Id(); + authorId = localState.getContact2AuthorId(); } - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + SessionId sessionId = localState.getSessionId(); MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); long time = msg.getLong(MESSAGE_TIME); String name = getOtherContact(localState, msg); @@ -309,12 +306,12 @@ public class IntroducerEngine return new IntroductionResponseReceivedEvent(contactId, ir); } - private boolean isContact1(BdfDictionary localState, BdfDictionary msg) - throws FormatException { + private boolean isContact1(IntroducerSessionState localState, + BdfDictionary msg) throws FormatException { byte[] group = msg.getRaw(GROUP_ID); - byte[] group1 = localState.getRaw(GROUP_ID_1); - byte[] group2 = localState.getRaw(GROUP_ID_2); + byte[] group1 = localState.getGroup1Id().getBytes(); + byte[] group2 = localState.getGroup2Id().getBytes(); if (Arrays.equals(group, group1)) { return true; @@ -325,69 +322,69 @@ public class IntroducerEngine } } - private String getMessagePartner(BdfDictionary localState, + private String getMessagePartner(IntroducerSessionState localState, BdfDictionary msg) throws FormatException { - String from = localState.getString(CONTACT_1); - if (Arrays.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { - from = localState.getString(CONTACT_2); + String from = localState.getContact1Name(); + if (Arrays.equals(msg.getRaw(GROUP_ID), + localState.getGroup2Id().getBytes())) { + from = localState.getContact2Name(); } return from; } - private String getOtherContact(BdfDictionary localState, BdfDictionary msg) - throws FormatException { + private String getOtherContact(IntroducerSessionState localState, + BdfDictionary msg) throws FormatException { - String to = localState.getString(CONTACT_2); - if (Arrays.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { - to = localState.getString(CONTACT_1); + String to = localState.getContact2Name(); + if (Arrays.equals(msg.getRaw(GROUP_ID), + localState.getGroup2Id().getBytes())) { + to = localState.getContact1Name(); } return to; } - private StateUpdate abortSession( - IntroducerProtocolState currentState, BdfDictionary localState) - throws FormatException { + private StateUpdate abortSession( + IntroducerProtocolState currentState, + IntroducerSessionState localState) throws FormatException { if (LOG.isLoggable(WARNING)) { LOG.warning("Aborting protocol session " + - Arrays.hashCode(localState.getRaw(SESSION_ID)) + + Arrays.hashCode(localState.getSessionId().getBytes()) + " in state " + currentState.name()); } - localState.put(STATE, ERROR.getValue()); + localState.setState(ERROR); List messages = new ArrayList(2); BdfDictionary msg1 = new BdfDictionary(); msg1.put(TYPE, TYPE_ABORT); - msg1.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); + msg1.put(SESSION_ID, localState.getSessionId()); + msg1.put(GROUP_ID, localState.getGroup1Id()); messages.add(msg1); BdfDictionary msg2 = new BdfDictionary(); msg2.put(TYPE, TYPE_ABORT); - msg2.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); + msg2.put(SESSION_ID, localState.getSessionId()); + msg2.put(GROUP_ID, localState.getGroup2Id()); messages.add(msg2); // send one abort event per contact List events = new ArrayList(2); - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); - ContactId contactId1 = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - ContactId contactId2 = - new ContactId(localState.getLong(CONTACT_ID_2).intValue()); + SessionId sessionId = localState.getSessionId(); + ContactId contactId1 = localState.getContact1Id(); + ContactId contactId2 = localState.getContact2Id(); Event event1 = new IntroductionAbortedEvent(contactId1, sessionId); events.add(event1); Event event2 = new IntroductionAbortedEvent(contactId2, sessionId); events.add(event2); - return new StateUpdate(false, false, - localState, messages, events); + return new StateUpdate(false, + false, localState, messages, events); } - private StateUpdate noUpdate( - BdfDictionary localState) throws FormatException { + private StateUpdate noUpdate( + IntroducerSessionState localState) throws FormatException { - return new StateUpdate(false, false, + return new StateUpdate(false, false, localState, Collections.emptyList(), Collections.emptyList()); } diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java index b8c414a47..9e1e1c36c 100644 --- a/briar-core/src/org/briarproject/introduction/IntroducerManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java @@ -3,6 +3,7 @@ package org.briarproject.introduction; import org.briarproject.api.Bytes; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.BdfDictionary; @@ -10,6 +11,7 @@ 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.IntroducerProtocolState; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -22,25 +24,12 @@ 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; -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_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.MSG; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY1; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY2; -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.SESSION_ID; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; @@ -67,8 +56,8 @@ class IntroducerManager { this.introductionGroupFactory = introductionGroupFactory; } - public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2) - throws FormatException, DbException { + public IntroducerSessionState initialize(Transaction txn, Contact c1, + Contact c2) throws FormatException, DbException { // create local message to keep engine state long now = clock.currentTimeMillis(); @@ -78,39 +67,31 @@ class IntroducerManager { Message m = clientHelper.createMessage( introductionGroupFactory.createLocalGroup().getId(), now, BdfList.of(salt)); - MessageId sessionId = m.getId(); + SessionId sessionId = new SessionId(m.getId().getBytes()); Group g1 = introductionGroupFactory.createIntroductionGroup(c1); Group g2 = introductionGroupFactory.createIntroductionGroup(c2); + IntroducerProtocolState state_value = IntroducerProtocolState.PREPARE_REQUESTS; + IntroducerSessionState state = new IntroducerSessionState(m.getId(), + sessionId, g1.getId(), g2.getId(), c1.getId(), c1.getAuthor().getId(), c1.getAuthor().getName(), + c2.getId(), c2.getAuthor().getId(), c2.getAuthor().getName(), state_value); - BdfDictionary d = new BdfDictionary(); - d.put(SESSION_ID, sessionId); - d.put(STORAGE_ID, sessionId); - d.put(STATE, PREPARE_REQUESTS.getValue()); - d.put(ROLE, ROLE_INTRODUCER); - d.put(GROUP_ID_1, g1.getId()); - d.put(GROUP_ID_2, g2.getId()); - d.put(CONTACT_1, c1.getAuthor().getName()); - d.put(CONTACT_2, c2.getAuthor().getName()); - d.put(CONTACT_ID_1, c1.getId().getInt()); - d.put(CONTACT_ID_2, c2.getId().getInt()); - d.put(AUTHOR_ID_1, c1.getAuthor().getId()); - d.put(AUTHOR_ID_2, c2.getAuthor().getId()); + BdfDictionary d = state.toBdfDictionary(); // save local state to database clientHelper.addLocalMessage(txn, m, d, false); - return d; + return state; } - public void makeIntroduction(Transaction txn, Contact c1, Contact c2, + void makeIntroduction(Transaction txn, Contact c1, Contact c2, String msg, long timestamp) throws DbException, FormatException { // TODO check for existing session with those contacts? // deny new introduction under which conditions? // initialize engine state - BdfDictionary localState = initialize(txn, c1, c2); + IntroducerSessionState localState = initialize(txn, c1, c2); // define action BdfDictionary localAction = new BdfDictionary(); @@ -124,11 +105,10 @@ class IntroducerManager { // start engine and process its state update IntroducerEngine engine = new IntroducerEngine(); - processStateUpdate(txn, - engine.onLocalAction(localState, localAction)); + processStateUpdate(txn, engine.onLocalAction(localState, localAction)); } - public void incomingMessage(Transaction txn, BdfDictionary state, + public void incomingMessage(Transaction txn, IntroducerSessionState state, BdfDictionary message) throws DbException, FormatException { IntroducerEngine engine = new IntroducerEngine(); @@ -137,12 +117,13 @@ class IntroducerManager { } private void processStateUpdate(Transaction txn, - IntroducerEngine.StateUpdate + IntroducerEngine.StateUpdate result) throws DbException, FormatException { // save new local state - MessageId storageId = new MessageId(result.localState.getRaw(STORAGE_ID)); - clientHelper.mergeMessageMetadata(txn, storageId, result.localState); + MessageId storageId = result.localState.getStorageId(); + clientHelper.mergeMessageMetadata(txn, storageId, + result.localState.toBdfDictionary()); // send messages for (BdfDictionary d : result.toSend) { @@ -155,7 +136,7 @@ class IntroducerManager { } } - public void abort(Transaction txn, BdfDictionary state) { + public void abort(Transaction txn, IntroducerSessionState state) { IntroducerEngine engine = new IntroducerEngine(); BdfDictionary localAction = new BdfDictionary(); diff --git a/briar-core/src/org/briarproject/introduction/IntroducerSessionState.java b/briar-core/src/org/briarproject/introduction/IntroducerSessionState.java new file mode 100644 index 000000000..322fa7606 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroducerSessionState.java @@ -0,0 +1,210 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroducerProtocolState; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +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_1; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY1; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY2; +import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_1; +import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_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.STATE; +import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; + +class IntroducerSessionState extends IntroductionState { + + private IntroducerProtocolState state; + + private byte[] publicKey1; + private MessageId response1; + private final GroupId group1Id; + private final ContactId contact1Id; + private final AuthorId contact1AuthorId; + private final String contact1Name; + + private byte[] publicKey2; + private MessageId response2; + private final GroupId group2Id; + private final ContactId contact2Id; + private final AuthorId contact2AuthorId; + private final String contact2Name; + + + IntroducerSessionState(MessageId storageId, SessionId sessionId, + GroupId group1Id, GroupId group2Id, ContactId contact1Id, + AuthorId contact1AuthorId, String contact1Name, + ContactId contact2Id, AuthorId contact2AuthorId, String contact2Name, + IntroducerProtocolState state){ + + super(sessionId, storageId); + + this.group2Id = group2Id; + this.group1Id = group1Id; + + this.contact2Id = contact2Id; + this.contact1Id = contact1Id; + + this.contact1AuthorId = contact1AuthorId; + this.contact2AuthorId = contact2AuthorId; + + this.contact1Name = contact1Name; + this.contact2Name = contact2Name; + + this.state = state; + this.response1 = null; + this.response2 = null; + this.publicKey1 = null; + this.publicKey2 = null; + + } + + public BdfDictionary toBdfDictionary() { + BdfDictionary d = super.toBdfDictionary(); + d.put(ROLE, ROLE_INTRODUCER); + + d.put(GROUP_ID_1, getGroup1Id()); + d.put(GROUP_ID_2, getGroup2Id()); + + d.put(STATE, getState().getValue()); + + d.put(CONTACT_1, contact1Name); + d.put(CONTACT_ID_1, contact1Id.getInt()); + d.put(AUTHOR_ID_1, contact1AuthorId); + + d.put(CONTACT_2, contact2Name); + d.put(CONTACT_ID_2, contact2Id.getInt()); + d.put(AUTHOR_ID_2, contact2AuthorId); + + if (publicKey1 != null) + d.put(PUBLIC_KEY1, publicKey1); + if (publicKey2 != null) + d.put(PUBLIC_KEY2, publicKey2); + + if (response1 != null) + d.put(RESPONSE_1, response1); + if (response2 != null) + d.put(RESPONSE_2, response2); + + return d; + } + + public static IntroducerSessionState fromBdfDictionary(BdfDictionary d) + throws FormatException{ + + MessageId storageId = new MessageId(d.getRaw(STORAGE_ID)); + SessionId sessionId = new SessionId(d.getRaw(SESSION_ID)); + + AuthorId aid1 = new AuthorId(d.getRaw(AUTHOR_ID_1)); + AuthorId aid2 = new AuthorId(d.getRaw(AUTHOR_ID_2)); + + String author1 = d.getString(CONTACT_1); + String author2 = d.getString(CONTACT_2); + ContactId cid1 = new ContactId(d.getLong(CONTACT_ID_1).intValue()); + ContactId cid2 = new ContactId(d.getLong(CONTACT_ID_2).intValue()); + + GroupId group1Id = new GroupId(d.getRaw(GROUP_ID_1)); + GroupId group2Id = new GroupId(d.getRaw(GROUP_ID_2)); + + + int stateno = d.getLong(STATE).intValue(); + IntroducerProtocolState state = IntroducerProtocolState.fromValue(stateno); + IntroducerSessionState newstate = new IntroducerSessionState(storageId, + sessionId, group1Id, group2Id, cid1, aid1, author1, cid2, aid2, + author2, state); + + if (d.containsKey(PUBLIC_KEY1)) + newstate.setPublicKey1(d.getRaw(PUBLIC_KEY1)); + + if (d.containsKey(PUBLIC_KEY2)) + newstate.setPublicKey2(d.getRaw(PUBLIC_KEY2)); + + if (d.containsKey(RESPONSE_1)) + newstate.setResponse1(new MessageId(d.getRaw(RESPONSE_1))); + if (d.containsKey(RESPONSE_2)) + newstate.setResponse2(new MessageId(d.getRaw(RESPONSE_2))); + + return newstate; + } + + GroupId getGroup2Id() { + return group2Id; + } + + public IntroducerProtocolState getState() { + return state; + } + + public void setState(IntroducerProtocolState state) { + this.state = state; + } + + MessageId getResponse1() { + return this.response1; + } + + void setResponse1(MessageId response1) { + this.response1 = response1; + } + + MessageId getResponse2() { + return this.response2; + } + + void setResponse2(MessageId response2) { + this.response2 = response2; + } + + public void setPublicKey1(byte[] publicKey1) { + this.publicKey1 = publicKey1; + } + + public void setPublicKey2(byte[] publicKey2) { + this.publicKey2 = publicKey2; + } + + public GroupId getGroup1Id() { + return this.group1Id; + } + public ContactId getContact2Id() { + return contact2Id; + } + + public AuthorId getContact2AuthorId() { + return contact2AuthorId; + } + + public String getContact2Name() { + return contact2Name; + } + + public String getContact1Name() { + return contact1Name; + } + + public ContactId getContact1Id() { + return contact1Id; + } + + public AuthorId getContact1AuthorId() { + return contact1AuthorId; + } + +} + + diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index 623a1ed37..543194882 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -44,32 +44,18 @@ import javax.inject.Inject; import static java.util.logging.Level.WARNING; import static org.briarproject.api.introduction.IntroduceeProtocolState.FINISHED; import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.api.introduction.IntroductionConstants.ANSWERED; -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; -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.EXISTS; 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.MSG; -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.READ; -import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; -import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; -import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_1; -import static org.briarproject.api.introduction.IntroductionConstants.RESPONSE_2; import static org.briarproject.api.introduction.IntroductionConstants.ROLE; import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; 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.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; @@ -166,16 +152,15 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook .getMessageMetadataAsDictionary(txn, gId, query); for (Map.Entry entry : map.entrySet()) { BdfDictionary d = entry.getValue(); - ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue()); - ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue()); + IntroducerSessionState stateobj = + IntroducerSessionState.fromBdfDictionary(d); + ContactId c1 = stateobj.getContact1Id(); + ContactId c2 = stateobj.getContact2Id(); if (c1.equals(c.getId()) || c2.equals(c.getId())) { - IntroducerProtocolState state = IntroducerProtocolState - .fromValue(d.getLong(STATE).intValue()); // abort protocol if still ongoing - if (IntroducerProtocolState.isOngoing(state)) { - introducerManager.abort(txn, d); - } + if (IntroducerProtocolState.isOngoing(stateobj.getState())) + introducerManager.abort(txn, stateobj); // also delete state if both contacts have been deleted if (c1.equals(c.getId())) { try { @@ -208,7 +193,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook */ @Override protected void incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary message) throws DbException { + BdfDictionary message) throws DbException { // Get message data and type GroupId groupId = m.getGroupId(); @@ -217,15 +202,24 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook // we are an introducee, need to initialize new state if (type == TYPE_REQUEST) { boolean stateExists = true; + SessionId sessionId; + IntroduceeSessionState state; try { - getSessionState(txn, groupId, message.getRaw(SESSION_ID), false); + sessionId = new SessionId(message.getRaw(SESSION_ID)); + } catch (FormatException e) { + LOG.warning("Introduction without SessionId received."); + deleteMessage(txn, m.getId()); + return; + } + try { + getSessionState(txn, groupId, sessionId.getBytes(), false); } catch (FormatException e) { stateExists = false; } - BdfDictionary state; try { if (stateExists) throw new FormatException(); - state = introduceeManager.initialize(txn, groupId, message); + state = introduceeManager + .initialize(txn, sessionId, groupId, message); } catch (FormatException e) { if (LOG.isLoggable(WARNING)) { LOG.warning( @@ -247,37 +241,43 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } // our role can be anything else if (type == TYPE_RESPONSE || type == TYPE_ACK || type == TYPE_ABORT) { - BdfDictionary state; + IntroductionState state; try { - state = getSessionState(txn, groupId, - message.getRaw(SESSION_ID)); + state = getSessionState(txn, groupId, message.getRaw(SESSION_ID)); } catch (FormatException e) { LOG.warning("Could not find state for message, deleting..."); deleteMessage(txn, m.getId()); return; } - long role = state.getLong(ROLE, -1L); try { - if (role == ROLE_INTRODUCER) { - introducerManager.incomingMessage(txn, state, message); - } else if (role == ROLE_INTRODUCEE) { - introduceeManager.incomingMessage(txn, state, message); + if (state instanceof IntroducerSessionState) { + introducerManager.incomingMessage(txn, + (IntroducerSessionState)state, message); + } else if (state instanceof IntroduceeSessionState) { + introduceeManager.incomingMessage(txn, + (IntroduceeSessionState)state, message); } else { if(LOG.isLoggable(WARNING)) { - LOG.warning("Unknown role '" + role + - "'. Deleting message..."); + LOG.warning("Unknown role '" + + state.getClass().getName() + + "'. Deleting message..."); deleteMessage(txn, m.getId()); } } } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state); - else introduceeManager.abort(txn, state); + if (state instanceof IntroducerSessionState) + introducerManager.abort(txn, + (IntroducerSessionState)state); + else introduceeManager.abort(txn, + (IntroduceeSessionState)state); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state); - else introduceeManager.abort(txn, state); + if (state instanceof IntroducerSessionState) + introducerManager.abort(txn, (IntroducerSessionState)state); + else introduceeManager.abort(txn, + (IntroduceeSessionState)state); } } else { // the message has been validated, so this should not happen @@ -310,8 +310,9 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook try { Contact c = db.getContact(txn, contactId); Group g = introductionGroupFactory.createIntroductionGroup(c); - BdfDictionary state = - getSessionState(txn, g.getId(), sessionId.getBytes()); + IntroduceeSessionState state = + (IntroduceeSessionState)getSessionState(txn, g.getId(), + sessionId.getBytes()); introduceeManager.acceptIntroduction(txn, state, timestamp); txn.setComplete(); @@ -329,10 +330,11 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook try { Contact c = db.getContact(txn, contactId); Group g = introductionGroupFactory.createIntroductionGroup(c); - BdfDictionary state = + IntroductionState state = getSessionState(txn, g.getId(), sessionId.getBytes()); - introduceeManager.declineIntroduction(txn, state, timestamp); + introduceeManager.declineIntroduction(txn, + (IntroduceeSessionState)state, timestamp); txn.setComplete(); } finally { db.endTransaction(txn); @@ -369,29 +371,39 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook // get session state SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); - BdfDictionary state = + IntroductionState state = getSessionState(txn, g, sessionId.getBytes()); - int role = state.getLong(ROLE).intValue(); boolean local; long time = msg.getLong(MESSAGE_TIME); boolean accepted = msg.getBoolean(ACCEPT, false); boolean read = msg.getBoolean(READ, false); AuthorId authorId; String name; + + int role; + if (state instanceof IntroducerSessionState) { + role = ROLE_INTRODUCER; + } else { + role = ROLE_INTRODUCEE; + } if (type == TYPE_RESPONSE) { if (role == ROLE_INTRODUCER) { - if (!concernsThisContact(contactId, messageId, state)) { + IntroducerSessionState iss = + (IntroducerSessionState)state; + if (!concernsThisContact(contactId, messageId, + iss)) { // this response is not from contactId continue; } local = false; - authorId = - getAuthorIdForIntroducer(contactId, state); - name = getNameForIntroducer(contactId, state); + authorId = getAuthorIdForIntroducer(contactId, iss); + name = getNameForIntroducer(contactId, iss); } else { - if (Arrays.equals(state.getRaw(NOT_OUR_RESPONSE), - messageId.getBytes())) { + IntroduceeSessionState iss = + (IntroduceeSessionState)state; + if (Arrays.equals(iss.getOtherResponseId(), + messageId.getBytes())) { // this response is not ours, // check if it was a decline if (!accepted) { @@ -403,9 +415,9 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } else { local = true; } - authorId = new AuthorId( - state.getRaw(REMOTE_AUTHOR_ID)); - name = state.getString(NAME); + + authorId = iss.getRemoteAuthorId(); + name = iss.getName(); } IntroductionResponse ir = new IntroductionResponse( sessionId, messageId, role, time, local, @@ -416,26 +428,26 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook String message; boolean answered, exists, introducesOtherIdentity; if (role == ROLE_INTRODUCER) { + IntroducerSessionState iss = + (IntroducerSessionState)state; local = true; - authorId = - getAuthorIdForIntroducer(contactId, state); - name = getNameForIntroducer(contactId, state); + authorId = getAuthorIdForIntroducer(contactId, iss); + name = getNameForIntroducer(contactId, iss); message = msg.getOptionalString(MSG); answered = false; exists = false; introducesOtherIdentity = false; } else { + IntroduceeSessionState iss = + (IntroduceeSessionState)state; local = false; - authorId = new AuthorId( - state.getRaw(REMOTE_AUTHOR_ID)); - name = state.getString(NAME); - message = state.getOptionalString(MSG); - boolean finished = state.getLong(STATE) == - FINISHED.getValue(); - answered = finished || state.getBoolean(ANSWERED); - exists = state.getBoolean(EXISTS); - introducesOtherIdentity = - state.getBoolean(REMOTE_AUTHOR_IS_US); + authorId = iss.getRemoteAuthorId(); + name = iss.getName(); + message = iss.getMessage(); + boolean finished = iss.getState() == FINISHED; + answered = finished || iss.getAnswered(); + exists = iss.getContactExists(); + introducesOtherIdentity = iss.getRemoteAuthorIsUs(); } IntroductionRequest ir = new IntroductionRequest( sessionId, messageId, role, time, local, @@ -458,38 +470,36 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } private String getNameForIntroducer(ContactId contactId, - BdfDictionary state) throws FormatException { + IntroducerSessionState state) throws FormatException { - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) - return state.getString(CONTACT_2); - if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue()) - return state.getString(CONTACT_1); + if (contactId.equals(state.getContact2Id())) + return state.getContact1Name(); + if (contactId.equals(state.getContact1Id())) + return state.getContact2Name(); throw new RuntimeException("Contact not part of this introduction session"); } private AuthorId getAuthorIdForIntroducer(ContactId contactId, - BdfDictionary state) throws FormatException { + IntroducerSessionState state) throws FormatException { - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) - return new AuthorId(state.getRaw(AUTHOR_ID_2)); - if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue()) - return new AuthorId(state.getRaw(AUTHOR_ID_1)); + if (contactId.equals(state.getContact1Id())) + return state.getContact2AuthorId(); + if (contactId.equals(state.getContact2Id())) + return state.getContact1AuthorId(); throw new RuntimeException("Contact not part of this introduction session"); } private boolean concernsThisContact(ContactId contactId, MessageId messageId, - BdfDictionary state) throws FormatException { + IntroducerSessionState state) throws FormatException { - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) { - return Arrays.equals(state.getRaw(RESPONSE_1, new byte[0]), - messageId.getBytes()); + if (contactId.equals(state.getContact1Id())) { + return state.getResponse1().equals(messageId); } else { - return Arrays.equals(state.getRaw(RESPONSE_2, new byte[0]), - messageId.getBytes()); + return state.getResponse2().equals(messageId); } } - private BdfDictionary getSessionState(Transaction txn, GroupId groupId, + private IntroductionState getSessionState(Transaction txn, GroupId groupId, byte[] sessionId, boolean warn) throws DbException, FormatException { @@ -503,7 +513,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook if (!g1.equals(groupId) && !g2.equals(groupId)) { throw new NoSuchMessageException(); } - return state; + return IntroductionState.fromBdfDictionary(state); } catch (NoSuchMessageException e) { // State not found directly, so iterate over all states // to find state for introducee @@ -514,7 +524,8 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) { BdfDictionary state = m.getValue(); GroupId g = new GroupId(state.getRaw(GROUP_ID)); - if (g.equals(groupId)) return state; + if (g.equals(groupId)) + return IntroductionState.fromBdfDictionary(state); } } if (warn && LOG.isLoggable(WARNING)) { @@ -526,7 +537,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook } } - private BdfDictionary getSessionState(Transaction txn, GroupId groupId, + private IntroductionState getSessionState(Transaction txn, GroupId groupId, byte[] sessionId) throws DbException, FormatException { return getSessionState(txn, groupId, sessionId, true); diff --git a/briar-core/src/org/briarproject/introduction/IntroductionState.java b/briar-core/src/org/briarproject/introduction/IntroductionState.java new file mode 100644 index 000000000..16ef508b3 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroductionState.java @@ -0,0 +1,64 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; + +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_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.ROLE_INTRODUCER; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID; + +// This class is not thread-safe +abstract class IntroductionState { + + private SessionId sessionId; + private final MessageId storageId; + + IntroductionState(SessionId sessionId, MessageId storageId) { + this.sessionId = sessionId; + this.storageId = storageId; + } + + public BdfDictionary toBdfDictionary() { + BdfDictionary d = new BdfDictionary(); + d.put(SESSION_ID, sessionId); + d.put(STORAGE_ID, getStorageId()); + return d; + } + + static IntroductionState fromBdfDictionary(BdfDictionary state) + throws FormatException { + + int role = state.getLong(ROLE).intValue(); + if (role == ROLE_INTRODUCER) { + return IntroducerSessionState.fromBdfDictionary(state); + } else if(role == ROLE_INTRODUCEE) { + return IntroduceeSessionState.fromBdfDictionary(state); + } else { + throw new FormatException(); + } + } + + public SessionId getSessionId() { + return sessionId; + } + + public MessageId getStorageId() { + return storageId; + } + + public void setSessionId(SessionId sessionId) { + this.sessionId = sessionId; + } + +} + + diff --git a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java index a7ee386a5..979308247 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java @@ -36,34 +36,31 @@ import org.junit.Test; import java.security.SecureRandom; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_LOCAL_RESPONSE; import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; +import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_RESPONSES; 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.MAC_LENGTH; 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.REMOTE_AUTHOR_IS_US; -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.briarproject.introduction.IntroduceeSessionState.fromBdfDictionary; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; public class IntroduceeManagerTest extends BriarTestCase { @@ -171,12 +168,18 @@ public class IntroduceeManagerTest extends BriarTestCase { msg.put(NAME, introducee2.getAuthor().getName()); msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - final BdfDictionary state = - initializeSessionState(txn, introductionGroup1.getId(), msg); + final IntroduceeSessionState state = + initializeSessionState(txn, sessionId, + introductionGroup1.getId(), msg); + + final BdfDictionary statedict = state.toBdfDictionary(); + statedict.put(STATE, AWAIT_RESPONSES.getValue()); + statedict.put(SESSION_ID, sessionId); + statedict.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY)); context.checking(new Expectations() {{ oneOf(clientHelper).mergeMessageMetadata(txn, - localStateMessage.getId(), state); + localStateMessage.getId(), statedict); }}); introduceeManager.incomingMessage(txn, state, msg); @@ -199,19 +202,31 @@ public class IntroduceeManagerTest extends BriarTestCase { 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()); + + final IntroduceeSessionState state = + initializeSessionState(txn, sessionId, + introductionGroup1.getId(), msg); + state.setTheirTime(time); + state.setTransport(new BdfDictionary()); + final BdfDictionary statedict = state.toBdfDictionary(); + state.setState(AWAIT_RESPONSES); + statedict.put(STATE, AWAIT_LOCAL_RESPONSE.getValue()); + statedict.put(ACCEPT, true); + statedict.put(E_PUBLIC_KEY, + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + statedict.put(NOT_OUR_RESPONSE, message1.getId().getBytes()); // 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(E_PUBLIC_KEY, statedict.getRaw(E_PUBLIC_KEY)); msg.put(TRANSPORT, new BdfDictionary()); + + context.checking(new Expectations() {{ oneOf(clientHelper).mergeMessageMetadata(txn, - localStateMessage.getId(), state); + localStateMessage.getId(), statedict); }}); introduceeManager.incomingMessage(txn, state, msg); @@ -221,9 +236,69 @@ public class IntroduceeManagerTest extends BriarTestCase { assertFalse(txn.isComplete()); } - private BdfDictionary initializeSessionState(final Transaction txn, - final GroupId groupId, final BdfDictionary msg) + @Test + public void testInitialSerialization() throws DbException, FormatException { + IntroduceeSessionState state = initializeDefaultSessionState(); + + BdfDictionary statedict = state.toBdfDictionary(); + assertEquals(statedict, fromBdfDictionary(statedict).toBdfDictionary()); +// assertEquals(state, fromBdfDictionary(statedict)); + } + + @Test + public void testFullSerialization() throws DbException, FormatException { + IntroduceeSessionState state = initializeDefaultSessionState(); + + state.setOurMac(TestUtils.getRandomBytes(42)); + // TODO use ALL setters here to make the test cover everything + state.setState(IntroduceeProtocolState.AWAIT_ACK); + state.setAccept(true); + state.setAnswered(true); + state.setOurTime(-1L); + state.setTheirTime(-2L); + state.setMessage(""); + state.setEPublicKey(TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + state.setTransport(new BdfDictionary()); + state.setContactExists(false); + state.setRemoteAuthorId(new AuthorId(TestUtils.getRandomId())); + state.setRemoteAuthorIsUs(false); + state.setOtherResponseId(TestUtils.getRandomId()); + state.setTask(-1); + state.setName(TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH)); + state.setOurPublicKey(TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + state.setOurPrivateKey(TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + state.setIntroducedPublicKey(TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + state.setIntroducedName(TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH)); + state.setMac(TestUtils.getRandomBytes(MAC_LENGTH)); + state.setSignature(TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH)); + state.setOurSignature(TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH)); + state.setOurTransport(new BdfDictionary()); + state.setNonce(TestUtils.getRandomBytes(32)); + state.setMacKey(TestUtils.getRandomBytes(32)); + + BdfDictionary statedict = state.toBdfDictionary(); + assertEquals(statedict, fromBdfDictionary(statedict).toBdfDictionary()); + } + + private IntroduceeSessionState initializeDefaultSessionState() 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()); + msg.put(TRANSPORT, new BdfDictionary()); + + return initializeSessionState(txn, sessionId, + introductionGroup1.getId(), msg); + } + + private IntroduceeSessionState initializeSessionState(final Transaction txn, + final SessionId sessionId, final GroupId groupId, + final BdfDictionary msg) throws DbException, FormatException { final SecureRandom secureRandom = context.mock(SecureRandom.class); final Bytes salt = new Bytes(new byte[64]); @@ -231,19 +306,17 @@ public class IntroduceeManagerTest extends BriarTestCase { 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()); - state.put(REMOTE_AUTHOR_IS_US, false); + final IntroduceeSessionState state = new IntroduceeSessionState( + localStateMessage.getId(), + sessionId, groupId, introducer.getId(), + introducer.getAuthor().getId(), introducer.getAuthor().getName(), + introducer.getLocalAuthorId(), AWAIT_REQUEST); + + state.setName(msg.getString(NAME)); + state.setContactExists(true); + state.setRemoteAuthorIsUs(false); + state.setRemoteAuthorId(introducee2.getAuthor().getId()); + final BdfDictionary statedict = state.toBdfDictionary(); context.checking(new Expectations() {{ oneOf(clock).currentTimeMillis(); @@ -279,10 +352,12 @@ public class IntroduceeManagerTest extends BriarTestCase { // store session state oneOf(clientHelper) - .addLocalMessage(txn, localStateMessage, state, false); + .addLocalMessage(txn, localStateMessage, statedict, + false); }}); - BdfDictionary result = introduceeManager.initialize(txn, groupId, msg); + IntroduceeSessionState result = introduceeManager.initialize(txn, + sessionId, 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 index caa0aeb84..71a828710 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroducerManagerTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroducerManagerTest.java @@ -19,6 +19,7 @@ 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.clients.SessionId; import org.briarproject.api.system.Clock; import org.jmock.Expectations; import org.jmock.Mockery; @@ -49,27 +50,30 @@ 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.assertEquals; 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; + private final Mockery context; + private final IntroducerManager introducerManager; + private final CryptoComponent cryptoComponent; + private final ClientHelper clientHelper; + private final IntroductionGroupFactory introductionGroupFactory; + private final MessageSender messageSender; + private final SecureRandom secureRandom; + private final Clock clock; + private final Contact introducee1; + private final Contact introducee2; + private final Group localGroup0; + private final Group introductionGroup1; + private final Group introductionGroup2; public IntroducerManagerTest() { context = new Mockery(); context.setImposteriser(ClassImposteriser.INSTANCE); messageSender = context.mock(MessageSender.class); + secureRandom = context.mock(SecureRandom.class); cryptoComponent = context.mock(CryptoComponent.class); clientHelper = context.mock(ClientHelper.class); clock = context.mock(Clock.class); @@ -107,48 +111,109 @@ public class IntroducerManagerTest extends BriarTestCase { } @Test - public void testMakeIntroduction() throws DbException, FormatException { + public void testInitializeSessionState() + 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 IntroducerSessionState state = + getState(msg, introducee1, introducee2); + + checkInitialisation(time, salt, msg, txn, state); + + IntroducerSessionState result = introducerManager.initialize(txn, + introducee1, introducee2); + assertEquals(state.toBdfDictionary(), result.toBdfDictionary()); + + context.assertIsSatisfied(); + } + + @Test + public void testMakeIntroduction() throws DbException, FormatException { + final Transaction txn = new Transaction(null, false); + + final long time = 42L; + final Bytes salt = new Bytes(new byte[64]); + final Message msg = new Message(new MessageId(TestUtils.getRandomId()), + localGroup0.getId(), time, TestUtils.getRandomBytes(64)); + + final IntroducerSessionState state = + getState(msg, introducee1, introducee2); + + checkInitialisation(time, salt, msg, txn, state); + + final IntroducerSessionState state2 = state; + state2.setState(AWAIT_RESPONSES); 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(SESSION_ID, state.getSessionId().getBytes()); + msg1.put(GROUP_ID, state.getGroup1Id().getBytes()); + msg1.put(NAME, state.getContact2Name()); 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(SESSION_ID, state.getSessionId().getBytes()); + msg2.put(GROUP_ID, state.getGroup2Id().getBytes()); + msg2.put(NAME, state.getContact1Name()); msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey()); final BdfDictionary msg2send = (BdfDictionary) msg2.clone(); msg2send.put(MESSAGE_TIME, time); + context.checking(new Expectations() {{ + // send message + oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), + state2.toBdfDictionary()); + oneOf(messageSender).sendMessage(txn, msg1send); + oneOf(messageSender).sendMessage(txn, msg2send); + }}); + + introducerManager + .makeIntroduction(txn, introducee1, introducee2, null, time); + + context.assertIsSatisfied(); + + assertFalse(txn.isComplete()); + } + + private IntroducerSessionState getState(Message msg, Contact c1, Contact c2) + throws FormatException { + + final BdfDictionary d = new BdfDictionary(); + d.put(SESSION_ID, new SessionId(msg.getId().getBytes())); + d.put(STORAGE_ID, msg.getId()); + d.put(STATE, PREPARE_REQUESTS.getValue()); + d.put(ROLE, ROLE_INTRODUCER); + d.put(GROUP_ID_1, introductionGroup1.getId()); + d.put(GROUP_ID_2, introductionGroup2.getId()); + d.put(CONTACT_1, introducee1.getAuthor().getName()); + d.put(CONTACT_2, introducee2.getAuthor().getName()); + d.put(CONTACT_ID_1, introducee1.getId().getInt()); + d.put(CONTACT_ID_2, introducee2.getId().getInt()); + d.put(AUTHOR_ID_1, introducee1.getAuthor().getId()); + d.put(AUTHOR_ID_2, introducee2.getAuthor().getId()); + + IntroducerSessionState state = + IntroducerSessionState.fromBdfDictionary(d); + + assertEquals(d, state.toBdfDictionary()); + + return state; + } + + private void checkInitialisation(final long time, final Bytes salt, + final Message msg, final Transaction txn, + final IntroducerSessionState state) + throws FormatException, DbException { + context.checking(new Expectations() {{ // initialize and store session state oneOf(clock).currentTimeMillis(); @@ -167,20 +232,14 @@ public class IntroducerManagerTest extends BriarTestCase { oneOf(introductionGroupFactory) .createIntroductionGroup(introducee2); will(returnValue(introductionGroup2)); - oneOf(clientHelper).addLocalMessage(txn, msg, state, false); + oneOf(clientHelper).addLocalMessage(txn, msg, + state.toBdfDictionary(), false); // send message - oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2); - oneOf(messageSender).sendMessage(txn, msg1send); - oneOf(messageSender).sendMessage(txn, msg2send); +//// 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() { diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java index 222ea050e..08595ff7c 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java @@ -6,6 +6,7 @@ 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.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; @@ -18,7 +19,7 @@ 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.clients.SessionId; +import org.briarproject.api.introduction.IntroducerProtocolState; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; @@ -37,39 +38,58 @@ 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.IntroduceeProtocolState.AWAIT_REQUEST; +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_1; import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID; +import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; import static org.briarproject.api.introduction.IntroductionConstants.ROLE; +import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; 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.STORAGE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.STATE; 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.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER; +import static org.briarproject.api.introduction.IntroductionConstants.EXISTS; +import static org.briarproject.api.introduction.IntroductionConstants.NO_TASK; +import static org.briarproject.api.introduction.IntroductionConstants.TASK; 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; + private final Mockery context; + private final IntroductionManagerImpl introductionManager; + private final IntroducerManager introducerManager; + private final IntroduceeManager introduceeManager; + private final DatabaseComponent db; + private final PrivateGroupFactory privateGroupFactory; + private final ClientHelper clientHelper; + private final MetadataEncoder metadataEncoder; + private final MessageQueueManager messageQueueManager; + private final IntroductionGroupFactory introductionGroupFactory; + private final Clock clock; + private final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + private final long time = 42L; + private final Contact introducee1; + private final Contact introducee2; + private final Group localGroup0; + private final Group introductionGroup1; + private final Group introductionGroup2; + private final Message message1; + private Transaction txn; public IntroductionManagerImplTest() { AuthorId authorId1 = new AuthorId(TestUtils.getRandomId()); @@ -145,14 +165,33 @@ public class IntroductionManagerImplTest extends BriarTestCase { assertTrue(txn.isComplete()); } - @Test + @Test public void testAcceptIntroduction() throws DbException, FormatException { final BdfDictionary state = BdfDictionary.of( + new BdfEntry(ROLE, ROLE_INTRODUCEE), new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), - new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) + new BdfEntry(GROUP_ID_2, introductionGroup2.getId()), + new BdfEntry(SESSION_ID, sessionId), + new BdfEntry(STORAGE_ID, sessionId), + new BdfEntry(AUTHOR_ID_1,introducee1.getAuthor().getId()), + new BdfEntry(CONTACT_1, introducee1.getAuthor().getName()), + new BdfEntry(CONTACT_ID_1, introducee1.getId().getInt()), + new BdfEntry(AUTHOR_ID_2,introducee2.getAuthor().getId() ), + new BdfEntry(CONTACT_2, introducee2.getAuthor().getName()), + new BdfEntry(CONTACT_ID_2, introducee2.getId().getInt()), + new BdfEntry(STATE, AWAIT_REQUEST.getValue()), + new BdfEntry(TIME, time), + new BdfEntry(OUR_TIME, time), + new BdfEntry(NAME, introducee1.getAuthor().getName()), + new BdfEntry(LOCAL_AUTHOR_ID, introducee1.getLocalAuthorId()), + new BdfEntry(INTRODUCER, introducee1.getAuthor().getName()), + new BdfEntry(EXISTS, false), + new BdfEntry(TASK, NO_TASK), + new BdfEntry(REMOTE_AUTHOR_IS_US, false) ); txn = new Transaction(null, false); + context.checking(new Expectations() {{ oneOf(db).startTransaction(false); will(returnValue(txn)); @@ -162,7 +201,8 @@ public class IntroductionManagerImplTest extends BriarTestCase { will(returnValue(introductionGroup1)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); - oneOf(introduceeManager).acceptIntroduction(txn, state, time); + oneOf(introduceeManager).acceptIntroduction(with(equal(txn)), + with(any(IntroduceeSessionState.class)), with(equal(time))); oneOf(db).endTransaction(txn); }}); @@ -176,8 +216,26 @@ public class IntroductionManagerImplTest extends BriarTestCase { @Test public void testDeclineIntroduction() throws DbException, FormatException { final BdfDictionary state = BdfDictionary.of( + new BdfEntry(ROLE, ROLE_INTRODUCEE), new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), - new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) + new BdfEntry(GROUP_ID_2, introductionGroup2.getId()), + new BdfEntry(SESSION_ID, sessionId), + new BdfEntry(STORAGE_ID, sessionId), + new BdfEntry(AUTHOR_ID_1,introducee1.getAuthor().getId()), + new BdfEntry(CONTACT_1, introducee1.getAuthor().getName()), + new BdfEntry(CONTACT_ID_1, introducee1.getId().getInt()), + new BdfEntry(AUTHOR_ID_2,introducee2.getAuthor().getId() ), + new BdfEntry(CONTACT_2, introducee2.getAuthor().getName()), + new BdfEntry(CONTACT_ID_2, introducee2.getId().getInt()), + new BdfEntry(STATE, AWAIT_REQUEST.getValue()), + new BdfEntry(TIME, time), + new BdfEntry(OUR_TIME, time), + new BdfEntry(NAME, introducee1.getAuthor().getName()), + new BdfEntry(LOCAL_AUTHOR_ID, introducee1.getLocalAuthorId()), + new BdfEntry(INTRODUCER, introducee1.getAuthor().getName()), + new BdfEntry(EXISTS, false), + new BdfEntry(TASK, NO_TASK), + new BdfEntry(REMOTE_AUTHOR_IS_US, false) ); txn = new Transaction(null, false); @@ -190,7 +248,8 @@ public class IntroductionManagerImplTest extends BriarTestCase { will(returnValue(introductionGroup1)); oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); - oneOf(introduceeManager).declineIntroduction(txn, state, time); + oneOf(introduceeManager).declineIntroduction(with(equal(txn)), + with(any(IntroduceeSessionState.class)), with(equal(time))); oneOf(db).endTransaction(txn); }}); @@ -238,19 +297,23 @@ public class IntroductionManagerImplTest extends BriarTestCase { final BdfDictionary msg = new BdfDictionary(); msg.put(TYPE, TYPE_REQUEST); - final BdfDictionary state = new BdfDictionary(); + final IntroduceeSessionState state = initializeIntroduceeSS(); txn = new Transaction(null, false); + final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + msg.put(SESSION_ID, sessionId); context.checking(new Expectations() {{ + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + new MessageId(sessionId.getBytes())); + will(returnValue(state.toBdfDictionary())); oneOf(introduceeManager) - .initialize(txn, introductionGroup1.getId(), msg); + .initialize(txn, sessionId, introductionGroup1.getId(), + msg); will(returnValue(state)); - oneOf(introduceeManager) - .incomingMessage(txn, state, msg); + oneOf(introduceeManager).incomingMessage(txn, state, msg); }}); - introductionManager - .incomingMessage(txn, message1, new BdfList(), msg); + introductionManager.incomingMessage(txn, message1, new BdfList(), msg); context.assertIsSatisfied(); assertFalse(txn.isComplete()); @@ -265,7 +328,8 @@ public class IntroductionManagerImplTest extends BriarTestCase { new BdfEntry(SESSION_ID, sessionId) ); - final BdfDictionary state = new BdfDictionary(); + final IntroducerSessionState sessionState = initializeIntroducerSS(); + final BdfDictionary state = sessionState.toBdfDictionary(); state.put(ROLE, ROLE_INTRODUCER); state.put(GROUP_ID_1, introductionGroup1.getId()); state.put(GROUP_ID_2, introductionGroup2.getId()); @@ -275,7 +339,8 @@ public class IntroductionManagerImplTest extends BriarTestCase { context.checking(new Expectations() {{ oneOf(clientHelper).getMessageMetadataAsDictionary(txn, sessionId); will(returnValue(state)); - oneOf(introducerManager).incomingMessage(txn, state, msg); + oneOf(introducerManager).incomingMessage(with(equal(txn)), + with(any(IntroducerSessionState.class)), with(equal(msg))); }}); introductionManager @@ -285,5 +350,44 @@ public class IntroductionManagerImplTest extends BriarTestCase { assertFalse(txn.isComplete()); } + private IntroduceeSessionState initializeIntroduceeSS() { + + final ContactId cid = new ContactId(0); + final AuthorId aid = new AuthorId(TestUtils.getRandomId()); + Author author = new Author(aid, "Introducer", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + final Contact introducer = new Contact(cid, author, aid, true, false); + final IntroduceeSessionState state = new IntroduceeSessionState( + new MessageId(TestUtils.getRandomId()), + new SessionId(TestUtils.getRandomId()), + new GroupId(TestUtils.getRandomId()), + introducer.getId(), introducer.getAuthor().getId(), + introducer.getAuthor().getName(), introducer.getLocalAuthorId(), + AWAIT_REQUEST); + + state.setContactExists(true); + state.setRemoteAuthorIsUs(false); + state.setRemoteAuthorId(introducee2.getAuthor().getId()); + state.setName(introducee2.getAuthor().getName()); + + return state; + } + + private IntroducerSessionState initializeIntroducerSS() { + final ContactId cid = new ContactId(0); + final AuthorId aid = new AuthorId(TestUtils.getRandomId()); + Author author = new Author(aid, "Introducer", + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + final Contact introducer = new Contact(cid, author, aid, true, false); + return new IntroducerSessionState( + new MessageId(TestUtils.getRandomId()), + new SessionId(TestUtils.getRandomId()), + new GroupId(TestUtils.getRandomId()), + new GroupId(TestUtils.getRandomId()), + introducer.getId(), introducer.getAuthor().getId(), introducer.getAuthor().getName(), + introducer.getId(), introducer.getAuthor().getId(), introducer.getAuthor().getName(), + IntroducerProtocolState.AWAIT_RESPONSES); + } + }