Compare commits

...

2 Commits

Author SHA1 Message Date
Santiago Torres
cef4c31e0b FIX: Addresses reviewer comments 2016-09-23 09:21:03 -04:00
Santiago Torres
62527a62c1 WIP: Replace session states in the Engine classes 2016-09-19 22:39:19 -04:00
13 changed files with 1625 additions and 563 deletions

View File

@@ -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
@@ -1142,6 +1142,7 @@ public class IntroductionIntegrationTest extends BriarTestCase {
}
}
} catch (DbException | IOException exception) {
msgWaiter.rethrow(exception);
eventWaiter.rethrow(exception);
} finally {
eventWaiter.resume();

View File

@@ -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;

View File

@@ -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,30 +34,17 @@ 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;
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
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.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.OUR_MAC;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
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;
import static org.briarproject.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.api.introduction.IntroductionConstants.TASK;
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;
@@ -69,23 +56,24 @@ 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<BdfDictionary, BdfDictionary, BdfDictionary> {
class IntroduceeEngine
implements
ProtocolEngine<BdfDictionary, IntroduceeSessionState, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(IntroduceeEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
public StateUpdate<IntroduceeSessionState, BdfDictionary> 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.wasLocallyAcceptedOrDeclined()) action = IntroduceeAction
.getLocal(type, localState.wasLocallyAccepted());
else action = IntroduceeAction.getLocal(type);
IntroduceeProtocolState nextState = currentState.next(action);
@@ -104,17 +92,17 @@ public class IntroduceeEngine
List<BdfDictionary> messages = new ArrayList<BdfDictionary>(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.wasLocallyAccepted());
if (localState.wasLocallyAccepted()) {
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 +110,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 +120,7 @@ public class IntroduceeEngine
throw new IllegalArgumentException();
}
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
return new StateUpdate<IntroduceeSessionState, BdfDictionary>(false,
false,
localState, messages, events);
} catch (FormatException e) {
@@ -141,12 +129,11 @@ public class IntroduceeEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
public StateUpdate<IntroduceeSessionState, BdfDictionary> 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 +149,12 @@ public class IntroduceeEngine
}
// update local session state with next protocol state
localState.put(STATE, nextState.getValue());
localState.setState(nextState);
List<BdfDictionary> messages;
List<Event> 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 +164,32 @@ 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.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<BdfDictionary, BdfDictionary>(true,
return new StateUpdate<IntroduceeSessionState, BdfDictionary>(
true,
false, localState,
Collections.<BdfDictionary>emptyList(),
Collections.<Event>emptyList());
@@ -209,62 +198,59 @@ public class IntroduceeEngine
else {
throw new IllegalArgumentException();
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
return new StateUpdate<IntroduceeSessionState, BdfDictionary>(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.setIntroducedName(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.setTheirResponseId(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.setTheirEphemeralPublicKey(msg.getRaw(E_PUBLIC_KEY));
localState.setOurTransportProperties(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.getIntroducedName() +
" with session ID " +
Arrays.hashCode(m.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(m.getRaw(GROUP_ID)));
}
@@ -272,20 +258,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.wasLocallyAccepted() ? "accept " : "decline ") +
"response in state " + state.name() +
" to " + localState.getString(INTRODUCER) +
" for " + localState.getString(NAME) + " with session ID " +
" to " + localState.getIntroducerName() +
" for " + localState.getIntroducedName() +
" 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 +280,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 +293,10 @@ 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.getIntroducedName() != null ?
" related to " + localState.getIntroducedName() :
"") +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
@@ -320,8 +308,8 @@ public class IntroduceeEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
public StateUpdate<IntroduceeSessionState, BdfDictionary> onMessageDelivered(
IntroduceeSessionState localState, BdfDictionary delivered) {
try {
return noUpdate(localState);
} catch (FormatException e) {
@@ -330,25 +318,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 +337,40 @@ public class IntroduceeEngine
return new IntroductionRequestReceivedEvent(contactId, ir);
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
IntroduceeProtocolState currentState, BdfDictionary localState)
throws FormatException {
private StateUpdate<IntroduceeSessionState, BdfDictionary> 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());
localState.getSessionId().hashCode() +
" 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<BdfDictionary> 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<Event> events = Collections.singletonList(event);
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
return new StateUpdate<IntroduceeSessionState, BdfDictionary>(false,
false, localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState) throws FormatException {
private StateUpdate<IntroduceeSessionState, BdfDictionary> noUpdate(
IntroduceeSessionState localState) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
return new StateUpdate<IntroduceeSessionState, BdfDictionary>(false,
false,
localState, Collections.<BdfDictionary>emptyList(),
Collections.<Event>emptyList());
}

View File

@@ -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;
@@ -44,44 +45,16 @@ import javax.inject.Inject;
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;
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;
import static org.briarproject.api.introduction.IntroductionConstants.MAC_KEY;
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.NONCE;
import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC;
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_SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_TRANSPORT;
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.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.NO_TASK;
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 +98,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 +119,12 @@ 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,11 +132,12 @@ 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.setIntroducedPublicKey(message.getRaw(PUBLIC_KEY));
// check if someone is trying to introduce us to ourselves
if(remoteAuthorId.equals(introducer.getLocalAuthorId())) {
if (remoteAuthorId.equals(introducer.getLocalAuthorId())) {
LOG.warning("Received Introduction Request to Ourselves");
throw new FormatException();
}
@@ -174,40 +145,40 @@ 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));
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<TransportId, TransportProperties> 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 +191,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 +210,16 @@ class IntroduceeManager {
}
private void processStateUpdate(Transaction txn, BdfDictionary msg,
IntroduceeEngine.StateUpdate<BdfDictionary, BdfDictionary>
IntroduceeEngine.StateUpdate<IntroduceeSessionState, BdfDictionary>
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 +242,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 +267,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 +278,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.getTheirEphemeralPublicKey();
// 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 +293,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 +310,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.setTheirNonce(theirNonce);
localState.setTheirMacKey(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 +330,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.getIntroducedName(),
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<TransportId, TransportProperties> 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,18 +382,19 @@ 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.getTheirNonce();
byte[] sig = localState.getSignature();
byte[] introducedPubKey = localState.getIntroducedPublicKey();
try {
// Parse the public key
KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(keyBytes);
KeyParser keyParser =
cryptoComponent.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(introducedPubKey);
// Verify the signature
Signature signature = cryptoComponent.getSignature();
signature.initVerify(key);
@@ -439,16 +413,16 @@ 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.getTheirMacKey();
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);
BdfList toSignList = BdfList.of(pubKey, ePubKey, tp, timestamp);
byte[] ePubKey = localState.getTheirEphemeralPublicKey();
BdfDictionary tp = localState.getOurTransportProperties();
long timestamp = localState.getTheirTime();
BdfList toSignList = BdfList.of(introducedPubKey, ePubKey, tp,
timestamp);
byte[] toSign = clientHelper.toByteArray(toSignList);
byte[] calculatedMac = cryptoComponent.mac(macKey, toSign);
if (!Arrays.equals(mac, calculatedMac)) {
@@ -458,8 +432,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 +449,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();

View File

@@ -0,0 +1,563 @@
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_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.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;
// This class is not thread-safe
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[] theirEphemeralPublicKey;
private byte[] mac;
private byte[] signature;
private byte[] ourMac;
private byte[] ourSignature;
private BdfDictionary ourTransport;
private byte[] theirNonce;
private byte[] theirMacKey;
private int task;
private String message;
private BdfDictionary ourTransportProperties;
// 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 introducedName;
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.ourTransportProperties = null;
this.introductionGroupId = groupId;
}
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, 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_ID_2, introducedId);
}
// TODO check if we really need three names and what this introducedName refers to
if (introducedName != null) d.put(NAME, introducedName);
if (remoteAuthorId != null)
d.put(REMOTE_AUTHOR_ID, remoteAuthorId);
d.put(LOCAL_AUTHOR_ID, localAuthorId);
if (ourTransportProperties != null)
d.put(TRANSPORT, ourTransportProperties);
if (ourPublicKey != null)
d.put(OUR_PUBLIC_KEY, ourPublicKey);
if (ourPrivateKey != null)
d.put(OUR_PRIVATE_KEY, ourPrivateKey);
else
d.put(OUR_PRIVATE_KEY, BdfDictionary.NULL_VALUE);
if (theirEphemeralPublicKey != null)
d.put(E_PUBLIC_KEY, theirEphemeralPublicKey);
else
d.put(E_PUBLIC_KEY, BdfDictionary.NULL_VALUE);
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 (theirNonce != null)
d.put(NONCE, theirNonce);
if (theirMacKey != null)
d.put(MAC_KEY, theirMacKey);
d.put(TIME, theirTime);
d.put(OUR_TIME, ourTime);
d.put(EXISTS, contactAlreadyExists);
d.put(TASK, task);
return d;
}
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));
GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
AuthorId authorId1 = new AuthorId(d.getRaw(AUTHOR_ID_1));
String introducerName = d.getString(INTRODUCER);
ContactId introducerId =
new ContactId(d.getLong(CONTACT_ID_1).intValue());
AuthorId introducerLocalAuthorId =
new AuthorId(d.getRaw(LOCAL_AUTHOR_ID));
int stateNumber = d.getLong(STATE).intValue();
IntroduceeProtocolState state =
IntroduceeProtocolState.fromValue(stateNumber);
IntroduceeSessionState sessionState =
new IntroduceeSessionState(storageId,
sessionId, groupId, introducerId, authorId1,
introducerName, introducerLocalAuthorId, state);
if (d.containsKey(AUTHOR_ID_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.setOurTransportProperties(d.getDictionary(TRANSPORT));
if (d.containsKey(OUR_PUBLIC_KEY))
sessionState.ourPublicKey = d.getRaw(OUR_PUBLIC_KEY);
if (d.containsKey(OUR_PRIVATE_KEY)&&
d.get(OUR_PRIVATE_KEY) != BdfDictionary.NULL_VALUE)
sessionState.ourPrivateKey = d.getRaw(OUR_PRIVATE_KEY);
if (d.containsKey(E_PUBLIC_KEY) &&
d.get(E_PUBLIC_KEY) != BdfDictionary.NULL_VALUE)
sessionState.theirEphemeralPublicKey = 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.setTheirResponseId(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));
if (d.containsKey(NAME))
sessionState.setIntroducedName(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.setTheirNonce(d.getRaw(NONCE));
if (d.containsKey(MAC_KEY))
sessionState.setTheirMacKey(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;
}
IntroduceeProtocolState getState() {
return state;
}
void setState(IntroduceeProtocolState state) {
this.state = state;
}
boolean wasLocallyAccepted() {
return accept;
}
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[] getTheirEphemeralPublicKey() {
return theirEphemeralPublicKey;
}
void setTheirEphemeralPublicKey(byte[] theirEphemeralPublicKey) {
this.theirEphemeralPublicKey = theirEphemeralPublicKey;
}
void setOurTransportProperties(BdfDictionary ourTransportProperties) {
this.ourTransportProperties = ourTransportProperties;
}
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 setTheirResponseId(byte[] otherResponse) {
this.otherResponseId = otherResponse;
}
String getMessage() {
return message;
}
void setTask(int task) {
this.task = task;
}
int getTask() {
return task;
}
boolean wasLocallyAcceptedOrDeclined() {
return accepted;
}
void setIntroducedName(String introducedName) {
this.introducedName = introducedName;
}
String getIntroducedName() {
return introducedName;
}
byte[] getOurPublicKey() {
return ourPublicKey;
}
void setOurPublicKey(byte[] ourPublicKey) {
this.ourPublicKey = ourPublicKey;
}
byte[] getOurPrivateKey() {
return ourPrivateKey;
}
void setOurPrivateKey(byte[] ourPrivateKey) {
this.ourPrivateKey = ourPrivateKey;
}
GroupId getIntroductionGroupId() {
return introductionGroupId;
}
byte[] getIntroducedPublicKey() {
return introducedPublicKey;
}
void setIntroducedPublicKey(byte[] introducedPublicKey) {
this.introducedPublicKey = introducedPublicKey;
}
ContactId getIntroducedId() {
return introducedId;
}
void setIntroducedId(
ContactId introducedId) {
this.introducedId = introducedId;
}
void setIntroducedAuthorId(
AuthorId introducedAuthorId) {
this.introducedAuthorId = introducedAuthorId;
}
AuthorId getLocalAuthorId() {
return localAuthorId;
}
void setLocalAuthorId(
AuthorId localAuthorId) {
this.localAuthorId = localAuthorId;
}
ContactId getIntroducerId() {
return introducerId;
}
void setMac(byte[] mac) {
this.mac = mac;
}
byte[] getMac() {
return mac;
}
void setSignature(byte[] signature) {
this.signature = signature;
}
byte[] getSignature() {
return signature;
}
void setOurMac(byte[] ourMac) {
this.ourMac = ourMac;
}
byte[] getOurMac() {
return ourMac;
}
void setOurSignature(byte[] ourSignature) {
this.ourSignature = ourSignature;
}
byte[] getOurSignature() {
return ourSignature;
}
void setOurTransport(BdfDictionary ourTransport) {
this.ourTransport = ourTransport;
}
BdfDictionary getOurTransport() {
return ourTransport;
}
void setTheirNonce(byte[] theirNonce) {
this.theirNonce = theirNonce;
}
byte[] getTheirNonce() {
return theirNonce;
}
void setTheirMacKey(byte[] theirMacKey) {
this.theirMacKey = theirMacKey;
}
byte[] getTheirMacKey() {
return this.theirMacKey;
}
BdfDictionary getOurTransportProperties() {
return ourTransportProperties;
}
String getIntroducerName() {
return introducerName;
}
}

View File

@@ -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,19 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
public class IntroducerEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
implements
ProtocolEngine<BdfDictionary, IntroducerSessionState, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(IntroducerEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
public StateUpdate<IntroducerSessionState, BdfDictionary> 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 +95,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<BdfDictionary> messages = new ArrayList<BdfDictionary>(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());
msg1.put(GROUP_ID, localState.getGroup1Id());
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 +113,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());
msg2.put(GROUP_ID, localState.getGroup2Id());
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 +125,8 @@ public class IntroducerEngine
logLocalAction(currentState, localState, msg2);
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
return new StateUpdate<IntroducerSessionState, BdfDictionary>(
false, false, localState, messages, events);
} else {
throw new IllegalArgumentException("Unknown Local Action");
}
@@ -134,12 +136,11 @@ public class IntroducerEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
public StateUpdate<IntroducerSessionState, BdfDictionary> 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 +167,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 +180,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 +188,31 @@ 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));
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));
events = Collections
.singletonList(getEvent(localState, msg));
} else return noUpdate(localState);
} else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
return new StateUpdate<IntroducerSessionState, BdfDictionary>(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 +223,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 +232,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 {
@@ -238,7 +245,8 @@ public class IntroducerEngine
String from = getMessagePartner(localState, msg);
String to = getOtherContact(localState, msg);
LOG.info("Received " + t + " in state " + currentState.name() + " from " +
LOG.info("Received " + t + " in state " + currentState.name() +
" from " +
from + " to " + to + " with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
@@ -249,15 +257,16 @@ public class IntroducerEngine
}
}
private List<BdfDictionary> forwardMessage(BdfDictionary localState,
private List<BdfDictionary> 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,34 +278,28 @@ public class IntroducerEngine
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
public StateUpdate<IntroducerSessionState, BdfDictionary> onMessageDelivered(
IntroducerSessionState localState, BdfDictionary delivered) {
try {
return noUpdate(localState);
}
catch (FormatException e) {
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
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 +312,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 +328,70 @@ 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<BdfDictionary, BdfDictionary> abortSession(
IntroducerProtocolState currentState, BdfDictionary localState)
throws FormatException {
private StateUpdate<IntroducerSessionState, BdfDictionary> abortSession(
IntroducerProtocolState currentState,
IntroducerSessionState localState) throws FormatException {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
localState.getSessionId().hashCode() +
" in state " + currentState.name());
}
localState.put(STATE, ERROR.getValue());
localState.setState(ERROR);
List<BdfDictionary> messages = new ArrayList<BdfDictionary>(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<Event> events = new ArrayList<Event>(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<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
return new StateUpdate<IntroducerSessionState, BdfDictionary>(false,
false, localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState) throws FormatException {
private StateUpdate<IntroducerSessionState, BdfDictionary> noUpdate(
IntroducerSessionState localState) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
return new StateUpdate<IntroducerSessionState, BdfDictionary>(false,
false,
localState, Collections.<BdfDictionary>emptyList(),
Collections.<Event>emptyList());
}

View File

@@ -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,32 +24,19 @@ 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;
class IntroducerManager {
private static final Logger LOG =
Logger.getLogger(IntroducerManager.class.getName());
Logger.getLogger(IntroducerManager.class.getName());
private final MessageSender messageSender;
private final ClientHelper clientHelper;
@@ -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,36 @@ 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 stateValue =
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(),
stateValue);
state.setPublicKey1(c1.getAuthor().getPublicKey());
state.setPublicKey2(c2.getAuthor().getPublicKey());
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 +110,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 +122,13 @@ class IntroducerManager {
}
private void processStateUpdate(Transaction txn,
IntroducerEngine.StateUpdate<BdfDictionary, BdfDictionary>
IntroducerEngine.StateUpdate<IntroducerSessionState, BdfDictionary>
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 +141,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();

View File

@@ -0,0 +1,219 @@
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;
// This class is not thread-safe
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;
}
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;
}
static IntroducerSessionState fromBdfDictionary(BdfDictionary d)
throws FormatException {
MessageId storageId = new MessageId(d.getRaw(STORAGE_ID));
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
AuthorId authorId1 = new AuthorId(d.getRaw(AUTHOR_ID_1));
AuthorId authorId2 = new AuthorId(d.getRaw(AUTHOR_ID_2));
String author1 = d.getString(CONTACT_1);
String author2 = d.getString(CONTACT_2);
ContactId contactId1 =
new ContactId(d.getLong(CONTACT_ID_1).intValue());
ContactId contactId2 =
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 stateNumber = d.getLong(STATE).intValue();
IntroducerProtocolState state = IntroducerProtocolState.fromValue(
stateNumber);
IntroducerSessionState newState = new IntroducerSessionState(storageId,
sessionId, group1Id, group2Id, contactId1, authorId1, author1,
contactId2,
authorId2,
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;
}
IntroducerProtocolState getState() {
return state;
}
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;
}
void setPublicKey1(byte[] publicKey1) {
this.publicKey1 = publicKey1;
}
void setPublicKey2(byte[] publicKey2) {
this.publicKey2 = publicKey2;
}
GroupId getGroup1Id() {
return this.group1Id;
}
ContactId getContact2Id() {
return contact2Id;
}
AuthorId getContact2AuthorId() {
return contact2AuthorId;
}
String getContact2Name() {
return contact2Name;
}
String getContact1Name() {
return contact1Name;
}
ContactId getContact1Id() {
return contact1Id;
}
AuthorId getContact1AuthorId() {
return contact1AuthorId;
}
}

View File

@@ -19,6 +19,7 @@ import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.introduction.IntroducerProtocolState;
import org.briarproject.introduction.IntroducerSessionState;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
@@ -44,32 +45,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 +153,15 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
.getMessageMetadataAsDictionary(txn, gId, query);
for (Map.Entry<MessageId, BdfDictionary> 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 +194,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
*/
@Override
protected void incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary message) throws DbException {
BdfDictionary message) throws DbException, FormatException{
// Get message data and type
GroupId groupId = m.getGroupId();
@@ -217,15 +203,19 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
// we are an introducee, need to initialize new state
if (type == TYPE_REQUEST) {
boolean stateExists = true;
SessionId sessionId;
IntroduceeSessionState state;
sessionId = new SessionId(message.getRaw(SESSION_ID));
try {
getSessionState(txn, groupId, message.getRaw(SESSION_ID), false);
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 +237,45 @@ 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());
}
throw new RuntimeException("Unknown role" +
state.getClass().getName());
}
} 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 +308,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 +328,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 +369,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 +413,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.getIntroducedName();
}
IntroductionResponse ir = new IntroductionResponse(
sessionId, messageId, role, time, local,
@@ -416,26 +426,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.getIntroducedName();
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 +468,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 +511,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 +522,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 +535,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);

View File

@@ -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;
}
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;
}
}

View File

@@ -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);
state.setIntroducedName(msg.getString(NAME));
state.setIntroducedPublicKey(msg.getRaw(PUBLIC_KEY));
final BdfDictionary statedict = state.toBdfDictionary();
statedict.put(STATE, AWAIT_RESPONSES.getValue());
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.setOurTransportProperties(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,67 @@ 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());
}
@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.setTheirEphemeralPublicKey(TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
state.setOurTransportProperties(new BdfDictionary());
state.setContactExists(false);
state.setRemoteAuthorId(new AuthorId(TestUtils.getRandomId()));
state.setRemoteAuthorIsUs(false);
state.setTheirResponseId(TestUtils.getRandomId());
state.setTask(-1);
state.setIntroducedName(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.setMac(TestUtils.getRandomBytes(MAC_LENGTH));
state.setSignature(TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH));
state.setOurSignature(TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH));
state.setOurTransport(new BdfDictionary());
state.setTheirNonce(TestUtils.getRandomBytes(32));
state.setTheirMacKey(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 +304,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.setContactExists(true);
state.setRemoteAuthorIsUs(false);
state.setRemoteAuthorId(introducee2.getAuthor().getId());
state.setIntroducedPublicKey(introducee2.getAuthor().getPublicKey());
final BdfDictionary statedict = state.toBdfDictionary();
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
@@ -279,10 +350,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;

View File

@@ -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;
@@ -42,6 +43,8 @@ import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.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;
@@ -49,27 +52,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 +113,130 @@ 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);
state.setPublicKey1(introducee1.getAuthor().getPublicKey());
state.setPublicKey2(introducee2.getAuthor().getPublicKey());
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);
state.setPublicKey1(introducee1.getAuthor().getPublicKey());
state.setPublicKey2(introducee2.getAuthor().getPublicKey());
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());
msg1.put(GROUP_ID, state.getGroup1Id());
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());
msg2.put(GROUP_ID, state.getGroup2Id());
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());
}
@Test
public void testFullSerialization() throws FormatException {
final Message msg = new Message(new MessageId(TestUtils.getRandomId()),
localGroup0.getId(), 0L, TestUtils.getRandomBytes(64));
IntroducerSessionState state = getState(msg, introducee1, introducee2);
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());
assertEquals(d, state.toBdfDictionary());
}
private IntroducerSessionState getState(Message msg, Contact c1, Contact c2)
throws FormatException {
IntroducerSessionState state = new IntroducerSessionState(msg.getId(),
new SessionId(msg.getId().getBytes()),
introductionGroup1.getId(),
introductionGroup2.getId(), c1.getId(), c1.getAuthor().getId(),
c1.getAuthor().getName(), c2.getId(), c2.getAuthor().getId(),
c2.getAuthor().getName(), PREPARE_REQUESTS);
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 +255,10 @@ public class IntroducerManagerTest extends BriarTestCase {
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2));
oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
// send message
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);
oneOf(messageSender).sendMessage(txn, msg1send);
oneOf(messageSender).sendMessage(txn, msg2send);
oneOf(clientHelper).addLocalMessage(txn, msg,
state.toBdfDictionary(), false);
}});
introducerManager
.makeIntroduction(txn, introducee1, introducee2, null, time);
context.assertIsSatisfied();
assertFalse(txn.isComplete());
}
private ClientId getClientId() {

View File

@@ -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;
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_INTRODUCER;
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.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());
@@ -148,21 +168,45 @@ public class IntroductionManagerImplTest extends BriarTestCase {
@Test
public void testAcceptIntroduction() throws DbException, FormatException {
final BdfDictionary state = BdfDictionary.of(
new BdfEntry(ROLE, ROLE_INTRODUCEE),
new BdfEntry(GROUP_ID, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
// FIXME: GROUP_ID, GROUP_ID_2 and GROUP_ID_1 are needed to
// FIXME: deserialize an *introducee*. Why is this?
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));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
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 +220,27 @@ 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, introductionGroup1.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);
@@ -186,11 +249,13 @@ public class IntroductionManagerImplTest extends BriarTestCase {
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
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);
}});
@@ -214,7 +279,8 @@ public class IntroductionManagerImplTest extends BriarTestCase {
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
introductionGroup1.getId());
@@ -238,19 +304,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,17 +335,16 @@ public class IntroductionManagerImplTest extends BriarTestCase {
new BdfEntry(SESSION_ID, sessionId)
);
final BdfDictionary state = new BdfDictionary();
state.put(ROLE, ROLE_INTRODUCER);
state.put(GROUP_ID_1, introductionGroup1.getId());
state.put(GROUP_ID_2, introductionGroup2.getId());
final IntroducerSessionState sessionState = initializeIntroducerSS();
final BdfDictionary state = sessionState.toBdfDictionary();
txn = new Transaction(null, false);
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 +354,46 @@ 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.setIntroducedName(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()),
introductionGroup1.getId(),
introductionGroup2.getId(),
introducer.getId(), introducer.getAuthor().getId(),
introducer.getAuthor().getName(),
introducer.getId(), introducer.getAuthor().getId(),
introducer.getAuthor().getName(),
IntroducerProtocolState.AWAIT_RESPONSES);
}
}