diff --git a/briar-api/src/org/briarproject/api/ProtocolEngine.java b/briar-api/src/org/briarproject/api/ProtocolEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..65d9fe52e178ac54903c2694cae6d58bf88411e2 --- /dev/null +++ b/briar-api/src/org/briarproject/api/ProtocolEngine.java @@ -0,0 +1,31 @@ +package org.briarproject.api; + +import org.briarproject.api.event.Event; + +import java.util.List; + +public interface ProtocolEngine<A, S, M> { + StateUpdate<S, M> onLocalAction(S localState, A action); + + StateUpdate<S, M> onMessageReceived(S localState, M received); + + StateUpdate<S, M> onMessageDelivered(S localState, M delivered); + + class StateUpdate<S, M> { + public final boolean deleteMessages; + public final boolean deleteState; + public final S localState; + public final List<M> toSend; + public final List<Event> toBroadcast; + + public StateUpdate(boolean deleteMessages, boolean deleteState, + S localState, List<M> toSend, List<Event> toBroadcast) { + + this.deleteMessages = deleteMessages; + this.deleteState = deleteState; + this.localState = localState; + this.toSend = toSend; + this.toBroadcast = toBroadcast; + } + } +} diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java index ba1d2c663025ed26a8dd04b0448616c5c6985d1c..a40a2b08c456db34f9b432795c2dfd01449fa54d 100644 --- a/briar-api/src/org/briarproject/api/contact/ContactManager.java +++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java @@ -16,12 +16,21 @@ public interface ContactManager { /** Registers a hook to be called whenever a contact is removed. */ void registerRemoveContactHook(RemoveContactHook hook); + /** + * Stores a contact within the given transaction associated with the given + * local and remote pseudonyms, and returns an ID for the contact. + */ + ContactId addContact(Transaction txn, Author remote, AuthorId local, + SecretKey master, long timestamp, boolean alice, boolean active) + throws DbException; + /** * Stores a contact associated with the given local and remote pseudonyms, * and returns an ID for the contact. */ - ContactId addContact(Author remote, AuthorId local, SecretKey master, - long timestamp, boolean alice, boolean active) throws DbException; + ContactId addContact(Author remote, AuthorId local, + SecretKey master, long timestamp, boolean alice, boolean active) + throws DbException; /** Returns the contact with the given ID. */ Contact getContact(ContactId c) throws DbException; @@ -35,6 +44,14 @@ public interface ContactManager { /** Marks a contact as active or inactive. */ void setContactActive(ContactId c, boolean active) throws DbException; + /** Return true if a contact with this name and public key already exists */ + boolean contactExists(Transaction txn, AuthorId remoteAuthorID, + AuthorId localAuthorId) throws DbException; + + /** Return true if a contact with this name and public key already exists */ + boolean contactExists(AuthorId remoteAuthorID, AuthorId localAuthorId) + throws DbException; + interface AddContactHook { void addingContact(Transaction txn, Contact c) throws DbException; } diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 2292bc0ba258a674251de8cdb5b6ebee64750251..a360fa98b61bb104e754a7bd42c6463a8e0918e0 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -162,6 +162,13 @@ public interface DatabaseComponent { Collection<ContactId> getContacts(Transaction txn, AuthorId a) throws DbException; + /** + * Returns true if the database contains the given contact for the given + * local pseudonym. + */ + boolean containsContact(Transaction txn, AuthorId remote, AuthorId local) + throws DbException; + /** * Returns the unique ID for this device. * <p/> diff --git a/briar-api/src/org/briarproject/api/event/ContactAddedEvent.java b/briar-api/src/org/briarproject/api/event/ContactAddedEvent.java index b8d29bf27cb526f830c371175e81db986b6eeb1f..0b2e5017cc88f8bacc762ed97d132773fe4b4093 100644 --- a/briar-api/src/org/briarproject/api/event/ContactAddedEvent.java +++ b/briar-api/src/org/briarproject/api/event/ContactAddedEvent.java @@ -6,12 +6,18 @@ import org.briarproject.api.contact.ContactId; public class ContactAddedEvent extends Event { private final ContactId contactId; + private final boolean active; - public ContactAddedEvent(ContactId contactId) { + public ContactAddedEvent(ContactId contactId, boolean active) { this.contactId = contactId; + this.active = active; } public ContactId getContactId() { return contactId; } + + public boolean isActive() { + return active; + } } diff --git a/briar-api/src/org/briarproject/api/event/IntroductionRequestReceivedEvent.java b/briar-api/src/org/briarproject/api/event/IntroductionRequestReceivedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..58473e08c81326327670b71cb5d25e54301a5014 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/IntroductionRequestReceivedEvent.java @@ -0,0 +1,26 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.introduction.IntroductionRequest; + +public class IntroductionRequestReceivedEvent extends Event { + + private final ContactId contactId; + private final IntroductionRequest introductionRequest; + + public IntroductionRequestReceivedEvent(ContactId contactId, + IntroductionRequest introductionRequest) { + + this.contactId = contactId; + this.introductionRequest = introductionRequest; + } + + public ContactId getContactId() { + return contactId; + } + + public IntroductionRequest getIntroductionRequest() { + return introductionRequest; + } + +} diff --git a/briar-api/src/org/briarproject/api/event/IntroductionResponseReceivedEvent.java b/briar-api/src/org/briarproject/api/event/IntroductionResponseReceivedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..2938c40db6423f4683de5956d9fa372a3fd58be1 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/IntroductionResponseReceivedEvent.java @@ -0,0 +1,25 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.introduction.IntroductionResponse; + +public class IntroductionResponseReceivedEvent extends Event { + + private final ContactId contactId; + private final IntroductionResponse introductionResponse; + + public IntroductionResponseReceivedEvent(ContactId contactId, + IntroductionResponse introductionResponse) { + + this.contactId = contactId; + this.introductionResponse = introductionResponse; + } + + public ContactId getContactId() { + return contactId; + } + + public IntroductionResponse getIntroductionResponse() { + return introductionResponse; + } +} diff --git a/briar-api/src/org/briarproject/api/event/IntroductionSucceededEvent.java b/briar-api/src/org/briarproject/api/event/IntroductionSucceededEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..dfbf3a3193748403a90d0455b7cc394fff4d479e --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/IntroductionSucceededEvent.java @@ -0,0 +1,16 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.Contact; + +public class IntroductionSucceededEvent extends Event { + + private final Contact contact; + + public IntroductionSucceededEvent(Contact contact) { + this.contact = contact; + } + + public Contact getContact() { + return contact; + } +} diff --git a/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java b/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java index c53a8363619ced08be3438d34026b9ba5aa1ef18..26216a873019ec9c6a1fcb5407fe06e718dd913f 100644 --- a/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java +++ b/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java @@ -1,5 +1,6 @@ package org.briarproject.api.event; +import org.briarproject.api.db.Metadata; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Message; diff --git a/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java new file mode 100644 index 0000000000000000000000000000000000000000..db50467f08b2fe29188f2c3b2352ab516bfd3652 --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java @@ -0,0 +1,43 @@ +package org.briarproject.api.introduction; + +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +public enum IntroduceeAction { + + LOCAL_ACCEPT, + LOCAL_DECLINE, + LOCAL_ABORT, + REMOTE_REQUEST, + REMOTE_ACCEPT, + REMOTE_DECLINE, + REMOTE_ABORT, + ACK; + + public static IntroduceeAction getRemote(int type, boolean accept) { + if (type == TYPE_REQUEST) return REMOTE_REQUEST; + if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT; + if (type == TYPE_RESPONSE) return REMOTE_DECLINE; + if (type == TYPE_ACK) return ACK; + if (type == TYPE_ABORT) return REMOTE_ABORT; + return null; + } + + public static IntroduceeAction getRemote(int type) { + return getRemote(type, true); + } + + public static IntroduceeAction getLocal(int type, boolean accept) { + if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT; + if (type == TYPE_RESPONSE) return LOCAL_DECLINE; + if (type == TYPE_ABORT) return LOCAL_ABORT; + return null; + } + + public static IntroduceeAction getLocal(int type) { + return getLocal(type, true); + } + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroduceeProtocolState.java b/briar-api/src/org/briarproject/api/introduction/IntroduceeProtocolState.java new file mode 100644 index 0000000000000000000000000000000000000000..8b46a0c1f6168b86b02cdd3c7520d262fe520384 --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroduceeProtocolState.java @@ -0,0 +1,76 @@ +package org.briarproject.api.introduction; + +import static org.briarproject.api.introduction.IntroduceeAction.ACK; +import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ACCEPT; +import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_DECLINE; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_ACCEPT; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_DECLINE; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_REQUEST; + +public enum IntroduceeProtocolState { + + ERROR(0), + AWAIT_REQUEST(1) { + @Override + public IntroduceeProtocolState next(IntroduceeAction a) { + if (a == REMOTE_REQUEST) return AWAIT_RESPONSES; + return ERROR; + } + }, + AWAIT_RESPONSES(2) { + @Override + public IntroduceeProtocolState next(IntroduceeAction a) { + if (a == REMOTE_ACCEPT) return AWAIT_LOCAL_RESPONSE; + if (a == REMOTE_DECLINE) return FINISHED; + if (a == LOCAL_ACCEPT) return AWAIT_REMOTE_RESPONSE; + if (a == LOCAL_DECLINE) return FINISHED; + return ERROR; + } + }, + AWAIT_REMOTE_RESPONSE(3) { + @Override + public IntroduceeProtocolState next(IntroduceeAction a) { + if (a == REMOTE_ACCEPT) return AWAIT_ACK; + if (a == REMOTE_DECLINE) return FINISHED; + return ERROR; + } + }, + AWAIT_LOCAL_RESPONSE(4) { + @Override + public IntroduceeProtocolState next(IntroduceeAction a) { + if (a == LOCAL_ACCEPT) return AWAIT_ACK; + if (a == LOCAL_DECLINE) return FINISHED; + return ERROR; + } + }, + AWAIT_ACK(5) { + @Override + public IntroduceeProtocolState next(IntroduceeAction a) { + if (a == ACK) return FINISHED; + return ERROR; + } + }, + FINISHED(6); + + private final int value; + + IntroduceeProtocolState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static IntroduceeProtocolState fromValue(int value) { + for (IntroduceeProtocolState s : values()) { + if (s.value == value) return s; + } + throw new IllegalArgumentException(); + } + + public IntroduceeProtocolState next(IntroduceeAction a) { + return this; + } + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroducerAction.java b/briar-api/src/org/briarproject/api/introduction/IntroducerAction.java new file mode 100644 index 0000000000000000000000000000000000000000..d20433876aec53c41c3ce361807137668292bceb --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroducerAction.java @@ -0,0 +1,46 @@ +package org.briarproject.api.introduction; + +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +public enum IntroducerAction { + + LOCAL_REQUEST, + LOCAL_ABORT, + REMOTE_ACCEPT_1, + REMOTE_ACCEPT_2, + REMOTE_DECLINE_1, + REMOTE_DECLINE_2, + REMOTE_ABORT, + ACK_1, + ACK_2; + + public static IntroducerAction getLocal(int type) { + if (type == TYPE_REQUEST) return LOCAL_REQUEST; + if (type == TYPE_ABORT) return LOCAL_ABORT; + return null; + } + + public static IntroducerAction getRemote(int type, boolean one, + boolean accept) { + + if (one) { + if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_1; + if (type == TYPE_RESPONSE) return REMOTE_DECLINE_1; + if (type == TYPE_ACK) return ACK_1; + } else { + if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_2; + if (type == TYPE_RESPONSE) return REMOTE_DECLINE_2; + if (type == TYPE_ACK) return ACK_2; + } + if (type == TYPE_ABORT) return REMOTE_ABORT; + return null; + } + + public static IntroducerAction getRemote(int type, boolean one) { + return getRemote(type, one, true); + } + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroducerProtocolState.java b/briar-api/src/org/briarproject/api/introduction/IntroducerProtocolState.java new file mode 100644 index 0000000000000000000000000000000000000000..d132e0d68a37bdd31dd25bc3f1e82a34d44a7105 --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroducerProtocolState.java @@ -0,0 +1,94 @@ +package org.briarproject.api.introduction; + +import static org.briarproject.api.introduction.IntroducerAction.ACK_1; +import static org.briarproject.api.introduction.IntroducerAction.ACK_2; +import static org.briarproject.api.introduction.IntroducerAction.LOCAL_REQUEST; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_1; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_2; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_1; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_2; + +public enum IntroducerProtocolState { + + ERROR(0), + PREPARE_REQUESTS(1) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == LOCAL_REQUEST) return AWAIT_RESPONSES; + return ERROR; + } + }, + AWAIT_RESPONSES(2) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == REMOTE_ACCEPT_1) return AWAIT_RESPONSE_2; + if (a == REMOTE_ACCEPT_2) return AWAIT_RESPONSE_1; + if (a == REMOTE_DECLINE_1) return FINISHED; + if (a == REMOTE_DECLINE_2) return FINISHED; + return ERROR; + } + }, + AWAIT_RESPONSE_1(3) { + public IntroducerProtocolState next(IntroducerAction a) { + if (a == REMOTE_ACCEPT_1) return AWAIT_ACKS; + if (a == REMOTE_DECLINE_1) return FINISHED; + return ERROR; + } + }, + AWAIT_RESPONSE_2(4) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == REMOTE_ACCEPT_2) return AWAIT_ACKS; + if (a == REMOTE_DECLINE_2) return FINISHED; + return ERROR; + } + }, + AWAIT_ACKS(5) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == ACK_1) return AWAIT_ACK_2; + if (a == ACK_2) return AWAIT_ACK_1; + return ERROR; + } + }, + AWAIT_ACK_1(6) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == ACK_1) return FINISHED; + return ERROR; + } + }, + AWAIT_ACK_2(7) { + @Override + public IntroducerProtocolState next(IntroducerAction a) { + if (a == ACK_2) return FINISHED; + return ERROR; + } + }, + FINISHED(8); + + private final int value; + + IntroducerProtocolState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static IntroducerProtocolState fromValue(int value) { + for (IntroducerProtocolState s : values()) { + if (s.value == value) return s; + } + throw new IllegalArgumentException(); + } + + public static boolean isOngoing(IntroducerProtocolState state) { + return state != FINISHED && state != ERROR; + } + + public IntroducerProtocolState next(IntroducerAction a) { + return this; + } +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..64bc309830b2f18ea2e17d8f115a842c2970b6fe --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -0,0 +1,68 @@ +package org.briarproject.api.introduction; + +public interface IntroductionConstants { + + /* Protocol roles */ + int ROLE_INTRODUCER = 0; + int ROLE_INTRODUCEE = 1; + + /* Message types */ + int TYPE_REQUEST = 1; + int TYPE_RESPONSE = 2; + int TYPE_ACK = 3; + int TYPE_ABORT = 4; + + /* Message Constants */ + String TYPE = "type"; + String GROUP_ID = "groupId"; + String SESSION_ID = "sessionId"; + String CONTACT = "contactId"; + String NAME = "name"; + String PUBLIC_KEY = "publicKey"; + String E_PUBLIC_KEY = "ephemeralPublicKey"; + String MSG = "msg"; + String ACCEPT = "accept"; + String TIME = "time"; + String DEVICE_ID = "deviceId"; + String TRANSPORT = "transport"; + String MESSAGE_ID = "messageId"; + String MESSAGE_TIME = "timestamp"; + + /* Introducer Local State Metadata */ + String STATE = "state"; + String ROLE = "role"; + String GROUP_ID_1 = "groupId1"; + String GROUP_ID_2 = "groupId2"; + String CONTACT_1 = "contact1"; + String CONTACT_2 = "contact2"; + String AUTHOR_ID_1 = "authorId1"; + String AUTHOR_ID_2 = "authorId2"; + String CONTACT_ID_1 = "contactId1"; + String CONTACT_ID_2 = "contactId2"; + String RESPONSE_1 = "response1"; + String RESPONSE_2 = "response2"; + String READ = "read"; + + /* Introduction Request Action */ + String PUBLIC_KEY1 = "publicKey1"; + String PUBLIC_KEY2 = "publicKey2"; + + /* Introducee Local State Metadata (without those already defined) */ + String STORAGE_ID = "storageId"; + String INTRODUCER = "introducer"; + String LOCAL_AUTHOR_ID = "localAuthorId"; + String REMOTE_AUTHOR_ID = "remoteAuthorId"; + String OUR_PUBLIC_KEY = "ourEphemeralPublicKey"; + String OUR_PRIVATE_KEY = "ourEphemeralPrivateKey"; + String OUR_TIME = "ourTime"; + String ADDED_CONTACT_ID = "addedContactId"; + String NOT_OUR_RESPONSE = "notOurResponse"; + String EXISTS = "contactExists"; + String ANSWERED = "answered"; + + String TASK = "task"; + int TASK_ADD_CONTACT = 0; + int TASK_ACTIVATE_CONTACT = 1; + int TASK_ABORT = 2; + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..882ccbf112a67be92c15f711fbc869384a3cb029 --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java @@ -0,0 +1,62 @@ +package org.briarproject.api.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.MessageId; + +import java.util.Collection; + +public interface IntroductionManager { + + /** Returns the unique ID of the introduction client. */ + ClientId getClientId(); + + /** + * sends two initial introduction messages + */ + void makeIntroduction(Contact c1, Contact c2, String msg) + throws DbException, FormatException; + + /** + * Accept an introduction that had been made + */ + void acceptIntroduction(final SessionId sessionId) + throws DbException, FormatException; + + /** + * Decline an introduction that had been made + */ + void declineIntroduction(final SessionId sessionId) + throws DbException, FormatException; + + /** + * Get all introduction messages for the contact with this contactId + */ + Collection<IntroductionMessage> getIntroductionMessages(ContactId contactId) + throws DbException; + + /** Marks an introduction message as read or unread. */ + void setReadFlag(MessageId m, boolean read) throws DbException; + + + /** Get the session state for the given session ID */ + BdfDictionary getSessionState(Transaction txn, byte[] sessionId) + throws DbException, FormatException; + + /** Gets the group used for introductions with Contact c */ + Group getIntroductionGroup(Contact c); + + /** Get the local group used to store session states */ + Group getLocalGroup(); + + /** Send an introduction message */ + void sendMessage(Transaction txn, BdfDictionary message) + throws DbException, FormatException; + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..6ac98793f208356cd66a3b4f6c41004f70727bc6 --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java @@ -0,0 +1,54 @@ +package org.briarproject.api.introduction; + +import org.briarproject.api.sync.MessageId; + +abstract public class IntroductionMessage { + + private final SessionId sessionId; + private final MessageId messageId; + private final long time; + private final boolean local, sent, seen, read; + + public IntroductionMessage(SessionId sessionId, MessageId messageId, + long time, boolean local, boolean sent, boolean seen, + boolean read) { + + this.sessionId = sessionId; + this.messageId = messageId; + this.time = time; + this.local = local; + this.sent = sent; + this.seen = seen; + this.read = read; + } + + public SessionId getSessionId() { + return sessionId; + } + + public long getTime() { + return time; + } + + public MessageId getMessageId() { + return messageId; + } + + public boolean isLocal() { + return local; + } + + public boolean isSent() { + return sent; + } + + public boolean isSeen() { + return seen; + } + + public boolean isRead() { + return read; + } + +} + diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionRequest.java b/briar-api/src/org/briarproject/api/introduction/IntroductionRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..facd7151fbf564b1afe1f1327c3b119781416dfe --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionRequest.java @@ -0,0 +1,36 @@ +package org.briarproject.api.introduction; + +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.sync.MessageId; + +public class IntroductionRequest extends IntroductionResponse { + + private final String message; + private final boolean answered, exists; + + public IntroductionRequest(SessionId sessionId, MessageId messageId, + long time, boolean local, boolean sent, boolean seen, boolean read, + AuthorId authorId, String name, boolean accepted, String message, + boolean answered, boolean exists) { + + super(sessionId, messageId, time, local, sent, seen, read, authorId, + name, accepted); + + this.message = message; + this.answered = answered; + this.exists = exists; + } + + public String getMessage() { + return message; + } + + public boolean wasAnswered() { + return answered; + } + + public boolean doesExist() { + return exists; + } + +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionResponse.java b/briar-api/src/org/briarproject/api/introduction/IntroductionResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..e73065bcc0800ea4d874521e84ed3fb8dafd2bcd --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionResponse.java @@ -0,0 +1,31 @@ +package org.briarproject.api.introduction; + +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.sync.MessageId; + +public class IntroductionResponse extends IntroductionMessage { + + private final AuthorId remoteAuthorId; + private final String name; + private final boolean accepted; + + public IntroductionResponse(SessionId sessionId, MessageId messageId, + long time, boolean local, boolean sent, boolean seen, boolean read, + AuthorId remoteAuthorId, String name, boolean accepted) { + + super(sessionId, messageId, time, local, sent, seen, read); + + this.remoteAuthorId = remoteAuthorId; + this.name = name; + this.accepted = accepted; + } + + public String getName() { + return name; + } + + public boolean wasAccepted() { + return accepted; + } + +} diff --git a/briar-api/src/org/briarproject/api/introduction/SessionId.java b/briar-api/src/org/briarproject/api/introduction/SessionId.java new file mode 100644 index 0000000000000000000000000000000000000000..d68bf9e0301ecf48ca1e61ab58a2772a7458846a --- /dev/null +++ b/briar-api/src/org/briarproject/api/introduction/SessionId.java @@ -0,0 +1,19 @@ +package org.briarproject.api.introduction; + +import org.briarproject.api.sync.MessageId; + +/** + * Type-safe wrapper for a byte array that uniquely identifies an + * introduction session. + */ +public class SessionId extends MessageId { + + public SessionId(byte[] id) { + super(id); + } + + @Override + public boolean equals(Object o) { + return o instanceof SessionId && super.equals(o); + } +} diff --git a/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java index 9dc655543950ec88500749ffc6c0d68c177d0cb9..445329ba95a6100c1def5d4a9f87363c5b3a3f94 100644 --- a/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java +++ b/briar-api/src/org/briarproject/api/properties/TransportPropertyManager.java @@ -4,6 +4,7 @@ import org.briarproject.api.DeviceId; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; import java.util.Map; @@ -13,7 +14,7 @@ public interface TransportPropertyManager { * Stores the given properties received while adding a contact - they will * be superseded by any properties synced from the contact. */ - void addRemoteProperties(ContactId c, DeviceId dev, + void addRemoteProperties(Transaction txn, ContactId c, DeviceId dev, Map<TransportId, TransportProperties> props) throws DbException; /** Returns the local transport properties for all transports. */ diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java index 00d46b1da768305a8b0cf484befdb60015a1bf58..275b938a6f7f7d995ec312ea120527bd4d7cda19 100644 --- a/briar-core/src/org/briarproject/CoreEagerSingletons.java +++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java @@ -4,6 +4,7 @@ import org.briarproject.contact.ContactModule; import org.briarproject.crypto.CryptoModule; import org.briarproject.db.DatabaseModule; import org.briarproject.forum.ForumModule; +import org.briarproject.introduction.IntroductionModule; import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.messaging.MessagingModule; import org.briarproject.plugins.PluginsModule; @@ -16,6 +17,7 @@ public interface CoreEagerSingletons { void inject(CryptoModule.EagerSingletons init); void inject(DatabaseModule.EagerSingletons init); void inject(ForumModule.EagerSingletons init); + void inject(IntroductionModule.EagerSingletons init); void inject(LifecycleModule.EagerSingletons init); void inject(MessagingModule.EagerSingletons init); void inject(PluginsModule.EagerSingletons init); diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java index 6303f210b3129e5a28e7121509aa423101c5c03c..d18e468f6ca31934a0565f50799c90e477a9c225 100644 --- a/briar-core/src/org/briarproject/CoreModule.java +++ b/briar-core/src/org/briarproject/CoreModule.java @@ -8,6 +8,7 @@ import org.briarproject.db.DatabaseModule; import org.briarproject.event.EventModule; import org.briarproject.forum.ForumModule; import org.briarproject.identity.IdentityModule; +import org.briarproject.introduction.IntroductionModule; import org.briarproject.invitation.InvitationModule; import org.briarproject.keyagreement.KeyAgreementModule; import org.briarproject.lifecycle.LifecycleModule; @@ -29,7 +30,7 @@ import dagger.Module; IdentityModule.class, EventModule.class, DataModule.class, ContactModule.class, PropertiesModule.class, TransportModule.class, SyncModule.class, SettingsModule.class, ClientsModule.class, - SystemModule.class, PluginsModule.class}) + SystemModule.class, PluginsModule.class, IntroductionModule.class}) public class CoreModule { public static void initEagerSingletons(CoreEagerSingletons c) { @@ -43,5 +44,6 @@ public class CoreModule { c.inject(new PropertiesModule.EagerSingletons()); c.inject(new SyncModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons()); + c.inject(new IntroductionModule.EagerSingletons()); } } diff --git a/briar-core/src/org/briarproject/clients/MessageQueueManagerImpl.java b/briar-core/src/org/briarproject/clients/MessageQueueManagerImpl.java index b2503b74028128dab4e706e8a226a6efe90968ce..eb7b0fc6aa5a7b2d23fba8118f0464525aa01b83 100644 --- a/briar-core/src/org/briarproject/clients/MessageQueueManagerImpl.java +++ b/briar-core/src/org/briarproject/clients/MessageQueueManagerImpl.java @@ -21,6 +21,7 @@ import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.util.ByteUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -63,9 +64,20 @@ class MessageQueueManagerImpl implements MessageQueueManager { QueueState queueState = loadQueueState(txn, queue.getId()); long queuePosition = queueState.outgoingPosition; queueState.outgoingPosition++; + if (LOG.isLoggable(INFO)) { + LOG.info("Sending message with position " + + queuePosition + " in group " + + queue.getId().hashCode() + " with transaction " + + txn.hashCode()); + } saveQueueState(txn, queue.getId(), queueState); QueueMessage q = queueMessageFactory.createMessage(queue.getId(), timestamp, queuePosition, body); + if (LOG.isLoggable(INFO)) { + LOG.info("First bytes of message: " + Arrays.toString( + Arrays.copyOfRange(q.getRaw(), 0, + QUEUE_MESSAGE_HEADER_LENGTH))); + } db.addLocalMessage(txn, q, queue.getClientId(), meta, true); return q; } @@ -196,7 +208,12 @@ class MessageQueueManagerImpl implements MessageQueueManager { if (LOG.isLoggable(INFO)) { LOG.info("Received message with position " + queuePosition + ", expecting " - + queueState.incomingPosition); + + queueState.incomingPosition + ". Received in group " + + m.getGroupId().hashCode() + " with transaction " + + txn.hashCode()); + LOG.info("First bytes of message: " + Arrays.toString( + Arrays.copyOfRange(m.getRaw(), 0, + QUEUE_MESSAGE_HEADER_LENGTH))); } if (queuePosition < queueState.incomingPosition) { // A message with this queue position has already been seen diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java index 7ff709b70c9062f79d9cb29c72979a405daadae0..23ec838802d8ad75c512e970720a185b566e254c 100644 --- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java +++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java @@ -46,6 +46,18 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook { removeHooks.add(hook); } + @Override + public ContactId addContact(Transaction txn, Author remote, AuthorId local, + SecretKey master,long timestamp, boolean alice, boolean active) + throws DbException { + ContactId c = db.addContact(txn, remote, local, active); + keyManager.addContact(txn, c, master, timestamp, alice); + Contact contact = db.getContact(txn, c); + for (AddContactHook hook : addHooks) + hook.addingContact(txn, contact); + return c; + } + @Override public ContactId addContact(Author remote, AuthorId local, SecretKey master, long timestamp, boolean alice, boolean active) @@ -53,11 +65,8 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook { ContactId c; Transaction txn = db.startTransaction(false); try { - c = db.addContact(txn, remote, local, active); - keyManager.addContact(txn, c, master, timestamp, alice); - Contact contact = db.getContact(txn, c); - for (AddContactHook hook : addHooks) - hook.addingContact(txn, contact); + c = addContact(txn, remote, local, master, timestamp, alice, + active); txn.setComplete(); } finally { db.endTransaction(txn); @@ -116,6 +125,26 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook { } } + @Override + public boolean contactExists(Transaction txn, AuthorId remoteAuthorID, + AuthorId localAuthorId) throws DbException { + return db.containsContact(txn, remoteAuthorID, localAuthorId); + } + + @Override + public boolean contactExists(AuthorId remoteAuthorID, + AuthorId localAuthorId) throws DbException { + boolean exists = false; + Transaction txn = db.startTransaction(true); + try { + exists = contactExists(txn, remoteAuthorID, localAuthorId); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return exists; + } + private void removeContact(Transaction txn, ContactId c) throws DbException { Contact contact = db.getContact(txn, c); diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index d00159903596c83da6d3750dc115571dcab6215e..d3e48748c6dea3c2fb745476cf30f9f470be8f5d 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -161,7 +161,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { if (db.containsContact(txn, remote.getId(), local)) throw new ContactExistsException(); ContactId c = db.addContact(txn, remote, local, active); - transaction.attach(new ContactAddedEvent(c)); + transaction.attach(new ContactAddedEvent(c, active)); if (active) transaction.attach(new ContactStatusChangedEvent(c, true)); return c; } @@ -342,6 +342,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return db.getContacts(txn, a); } + public boolean containsContact(Transaction transaction, AuthorId remote, + AuthorId local) throws DbException { + T txn = unbox(transaction); + if (!db.containsLocalAuthor(txn, local)) + throw new NoSuchLocalAuthorException(); + return db.containsContact(txn, remote, local); + } + public DeviceId getDeviceId(Transaction transaction) throws DbException { T txn = unbox(transaction); return db.getDeviceId(txn); diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..7be3184086617a1e34fff0aab621b3f390e3fb3f --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java @@ -0,0 +1,371 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.ProtocolEngine; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.IntroductionRequestReceivedEvent; +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.introduction.SessionId; +import org.briarproject.api.sync.MessageId; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ABORT; +import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ACCEPT; +import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_DECLINE; +import static org.briarproject.api.introduction.IntroduceeAction.REMOTE_ABORT; +import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_ACK; +import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REMOTE_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.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.DEVICE_ID; +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.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_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.SESSION_ID; +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; +import static org.briarproject.api.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +public class IntroduceeEngine + implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { + + private static final Logger LOG = + Logger.getLogger(IntroduceeEngine.class.getName()); + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( + BdfDictionary localState, BdfDictionary localAction) { + + try { + IntroduceeProtocolState currentState = + getState(localState.getLong(STATE)); + int type = localAction.getLong(TYPE).intValue(); + IntroduceeAction action; + if (localState.containsKey(ACCEPT)) action = IntroduceeAction + .getLocal(type, localState.getBoolean(ACCEPT)); + else action = IntroduceeAction.getLocal(type); + IntroduceeProtocolState nextState = currentState.next(action); + + if (action == LOCAL_ABORT && currentState != ERROR) { + return abortSession(currentState, localState); + } + + if (nextState == ERROR) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Error: Invalid action in state " + + currentState.name()); + } + if (currentState == ERROR) return noUpdate(localState); + else abortSession(currentState, localState); + } + + if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { + localState.put(STATE, nextState.getValue()); + localState.put(ANSWERED, true); + List<BdfDictionary> messages = new ArrayList<BdfDictionary>(1); + // 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(DEVICE_ID, localAction.getRaw(DEVICE_ID)); + msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT)); + } + messages.add(msg); + logAction(currentState, localState, msg); + + if (nextState == AWAIT_ACK) { + localState.put(TASK, TASK_ADD_CONTACT); + // also send ACK, because we already have the other response + BdfDictionary ack = getAckMessage(localState); + messages.add(ack); + } + List<Event> events = Collections.emptyList(); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, + false, + localState, messages, events); + } else { + throw new IllegalArgumentException(); + } + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( + BdfDictionary localState, BdfDictionary msg) { + + try { + IntroduceeProtocolState currentState = + getState(localState.getLong(STATE)); + int type = msg.getLong(TYPE).intValue(); + IntroduceeAction action = IntroduceeAction.getRemote(type); + IntroduceeProtocolState nextState = currentState.next(action); + + logMessageReceived(currentState, nextState, localState, type, msg); + + if (nextState == ERROR) { + if (currentState != ERROR && action != REMOTE_ABORT) { + return abortSession(currentState, localState); + } else { + return noUpdate(localState); + } + } + + // update local session state with next protocol state + localState.put(STATE, nextState.getValue()); + 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)); + + addRequestData(localState, msg); + messages = Collections.emptyList(); + events = Collections.singletonList(getEvent(localState, msg)); + } + // we had the request and now one response came in _OR_ + // 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()); + + addResponseData(localState, msg); + if (nextState == AWAIT_ACK) { + localState.put(TASK, TASK_ADD_CONTACT); + messages = Collections + .singletonList(getAckMessage(localState)); + } else { + 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); + messages = Collections.emptyList(); + events = Collections.emptyList(); + } + // we are done (probably declined response) and ignore this message + else if (currentState == FINISHED) { + return noUpdate(localState); + } + // this should not happen + else { + throw new IllegalArgumentException(); + } + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + private void addRequestData(BdfDictionary localState, BdfDictionary msg) + throws FormatException { + + localState.put(NAME, msg.getString(NAME)); + localState.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY)); + if (msg.containsKey(MSG)) { + localState.put(MSG, msg.getString(MSG)); + } + } + + private void addResponseData(BdfDictionary 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)); + + if (msg.getBoolean(ACCEPT)) { + localState.put(TIME, msg.getLong(TIME)); + localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY)); + localState.put(DEVICE_ID, msg.getRaw(DEVICE_ID)); + localState.put(TRANSPORT, msg.getDictionary(TRANSPORT)); + } + } + + private BdfDictionary getAckMessage(BdfDictionary 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)); + + if (LOG.isLoggable(INFO)) { + LOG.info("Sending ACK " + " to " + + localState.getString(INTRODUCER) + " for " + + localState.getString(NAME) + " with session ID " + + Arrays.hashCode(m.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(m.getRaw(GROUP_ID))); + } + return m; + } + + private void logAction(IntroduceeProtocolState state, + BdfDictionary localState, BdfDictionary msg) { + + if (!LOG.isLoggable(INFO)) return; + + try { + LOG.info("Sending " + + (localState.getBoolean(ACCEPT) ? "accept " : "decline ") + + "response in state " + state.name() + + " to " + localState.getString(INTRODUCER) + + " for " + localState.getString(NAME) + " 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() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private void logMessageReceived(IntroduceeProtocolState currentState, + IntroduceeProtocolState nextState, BdfDictionary localState, + int type, BdfDictionary msg) { + + if (!LOG.isLoggable(INFO)) return; + + try { + String t = "unknown"; + if (type == TYPE_REQUEST) t = "Introduction"; + else if (type == TYPE_RESPONSE) t = "Response"; + else if (type == TYPE_ACK) t = "ACK"; + 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) : "") + + " with session ID " + + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + + "Moving on to state " + nextState.name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( + BdfDictionary localState, BdfDictionary delivered) { + try { + return noUpdate(localState); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; + } + } + + private IntroduceeProtocolState getState(Long state) { + return IntroduceeProtocolState.fromValue(state.intValue()); + } + + 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(REMOTE_AUTHOR_ID)); + + SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + 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); + + IntroductionRequest ir = new IntroductionRequest(sessionId, messageId, + time, false, false, false, false, authorId, name, false, + message, false, exists); + return new IntroductionRequestReceivedEvent(contactId, ir); + } + + private StateUpdate<BdfDictionary, BdfDictionary> abortSession( + IntroduceeProtocolState currentState, BdfDictionary localState) + throws FormatException { + + if (LOG.isLoggable(WARNING)) { + LOG.warning("Aborting protocol session " + + Arrays.hashCode(localState.getRaw(SESSION_ID)) + + " in state " + currentState.name()); + } + + localState.put(STATE, ERROR.getValue()); + localState.put(TASK, 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)); + List<BdfDictionary> messages = Collections.singletonList(msg); + // TODO inform about protocol abort via new Event? + List<Event> events = Collections.emptyList(); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } + + private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( + BdfDictionary localState) throws FormatException { + + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, new ArrayList<BdfDictionary>(0), + new ArrayList<Event>(0)); + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2049fba3ff5d636ee2acb8b48090184b3b77c715 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java @@ -0,0 +1,406 @@ +package org.briarproject.introduction; + + +import org.briarproject.api.Bytes; +import org.briarproject.api.DeviceId; +import org.briarproject.api.FormatException; +import org.briarproject.api.TransportId; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.KeyPair; +import org.briarproject.api.crypto.KeyParser; +import org.briarproject.api.crypto.PrivateKey; +import org.briarproject.api.crypto.PublicKey; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.IntroductionSucceededEvent; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.properties.TransportProperties; +import org.briarproject.api.properties.TransportPropertyManager; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +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.DEVICE_ID; +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.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; +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.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.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; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +class IntroduceeManager { + + + private static final Logger LOG = + Logger.getLogger(IntroduceeManager.class.getName()); + + private final DatabaseComponent db; + private final IntroductionManager introductionManager; + private final ClientHelper clientHelper; + private final Clock clock; + private final CryptoComponent cryptoComponent; + private final TransportPropertyManager transportPropertyManager; + private final AuthorFactory authorFactory; + private final ContactManager contactManager; + + IntroduceeManager(DatabaseComponent db, + IntroductionManager introductionManager, ClientHelper clientHelper, + Clock clock, CryptoComponent cryptoComponent, + TransportPropertyManager transportPropertyManager, + AuthorFactory authorFactory, ContactManager contactManager) { + + this.db = db; + this.introductionManager = introductionManager; + this.clientHelper = clientHelper; + this.clock = clock; + this.cryptoComponent = cryptoComponent; + this.transportPropertyManager = transportPropertyManager; + this.authorFactory = authorFactory; + this.contactManager = contactManager; + } + + public BdfDictionary initialize(Transaction txn, GroupId groupId, + BdfDictionary message) throws DbException, FormatException { + + // create local message to keep engine state + long now = clock.currentTimeMillis(); + Bytes salt = new Bytes(new byte[64]); + cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); + + Message localMsg = clientHelper + .createMessage(introductionManager.getLocalGroup().getId(), now, + BdfList.of(salt)); + MessageId storageId = localMsg.getId(); + + // find out who is introducing us + BdfDictionary gd = + clientHelper.getGroupMetadataAsDictionary(txn, groupId); + ContactId introducerId = + 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, new byte[0]); + d.put(ANSWERED, false); + + // check if the contact we are introduced to does already exist + AuthorId remoteAuthorId = authorFactory + .createAuthor(message.getString(NAME), + message.getRaw(PUBLIC_KEY)).getId(); + boolean exists = contactManager.contactExists(txn, remoteAuthorId, + introducer.getLocalAuthorId()); + d.put(EXISTS, exists); + d.put(REMOTE_AUTHOR_ID, remoteAuthorId); + + // save local state to database + clientHelper.addLocalMessage(txn, localMsg, + introductionManager.getClientId(), d, false); + + return d; + } + + public void incomingMessage(Transaction txn, BdfDictionary state, + BdfDictionary message) throws DbException, FormatException { + + IntroduceeEngine engine = new IntroduceeEngine(); + processStateUpdate(txn, engine.onMessageReceived(state, message)); + } + + public void acceptIntroduction(Transaction txn, + final SessionId sessionId) throws DbException, FormatException { + + BdfDictionary state = + introductionManager.getSessionState(txn, sessionId.getBytes()); + + // get data to connect and derive a shared secret later + long now = clock.currentTimeMillis(); + byte[] deviceId = db.getDeviceId(txn).getBytes(); + KeyPair keyPair = cryptoComponent.generateAgreementKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + byte[] privateKey = keyPair.getPrivate().getEncoded(); + Map<TransportId, TransportProperties> transportProperties = + transportPropertyManager.getLocalProperties(); + + // 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); + + // define action + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, TYPE_RESPONSE); + localAction.put(DEVICE_ID, deviceId); + localAction.put(TRANSPORT, + encodeTransportProperties(transportProperties)); + + // start engine and process its state update + IntroduceeEngine engine = new IntroduceeEngine(); + processStateUpdate(txn, + engine.onLocalAction(state, localAction)); + } + + public void declineIntroduction(Transaction txn, final SessionId sessionId) + throws DbException, FormatException { + + BdfDictionary state = + introductionManager.getSessionState(txn, sessionId.getBytes()); + + // update session state + state.put(ACCEPT, false); + + // define action + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, TYPE_RESPONSE); + + // start engine and process its state update + IntroduceeEngine engine = new IntroduceeEngine(); + processStateUpdate(txn, + engine.onLocalAction(state, localAction)); + } + + private void processStateUpdate(Transaction txn, + IntroduceeEngine.StateUpdate<BdfDictionary, 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); + + // send messages + for (BdfDictionary d : result.toSend) { + introductionManager.sendMessage(txn, d); + } + + // broadcast events + for (Event event : result.toBroadcast) { + txn.attach(event); + } + } + + private void performTasks(Transaction txn, BdfDictionary localState) + throws FormatException, DbException { + + if (!localState.containsKey(TASK)) return; + + // remember task and remove it from localState + long task = localState.getLong(TASK); + localState.put(TASK, BdfDictionary.NULL_VALUE); + + + + if (task == TASK_ADD_CONTACT) { + if (localState.getBoolean(EXISTS)) { + // we have this contact already, so do not perform actions + LOG.info("We have this contact already, do not add"); + return; + } + + LOG.info("Adding contact in inactive state"); + + // get all keys + KeyParser keyParser = cryptoComponent.getAgreementKeyParser(); + byte[] publicKeyBytes; + PublicKey publicKey; + PrivateKey privateKey; + try { + publicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY); + publicKey = keyParser + .parsePublicKey(publicKeyBytes); + privateKey = keyParser.parsePrivateKey( + localState.getRaw(OUR_PRIVATE_KEY)); + } catch (GeneralSecurityException e) { + if (LOG.isLoggable(WARNING)) { + LOG.log(WARNING, e.toString(), e); + } + // 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); + + // figure out who takes which role by comparing public keys + int comp = Bytes.COMPARATOR.compare(new Bytes(publicKeyBytes), + new Bytes(theirEphemeralKey)); + boolean alice = comp < 0; + + // The master secret is derived from the local ephemeral key pair + // and the remote ephemeral public key + SecretKey secretKey; + try { + secretKey = cryptoComponent + .deriveMasterSecret(theirEphemeralKey, keyPair, alice); + } catch (GeneralSecurityException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + // we can not continue without the shared secret + throw new FormatException(); + } + + // The agreed timestamp is the minimum of the peers' timestamps + long ourTime = localState.getLong(OUR_TIME); + long theirTime = localState.getLong(TIME); + long timestamp = Math.min(ourTime, theirTime); + + // Add the contact to the database + AuthorId localAuthorId = + new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID)); + Author remoteAuthor = authorFactory + .createAuthor(localState.getString(NAME), + localState.getRaw(PUBLIC_KEY)); + ContactId contactId = contactManager + .addContact(txn, remoteAuthor, localAuthorId, secretKey, + timestamp, alice, false); + + // Update local state with ContactId, so we know what to activate + localState.put(ADDED_CONTACT_ID, contactId.getInt()); + + // let the transport manager know how to connect to the contact + DeviceId deviceId = new DeviceId(localState.getRaw(DEVICE_ID)); + Map<TransportId, TransportProperties> transportProperties = + parseTransportProperties(localState); + transportPropertyManager.addRemoteProperties(txn, contactId, + deviceId, 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, BdfDictionary.NULL_VALUE); + } + + // we sent and received an ACK, so activate contact + if (task == TASK_ACTIVATE_CONTACT) { + if (!localState.getBoolean(EXISTS) && + localState.containsKey(ADDED_CONTACT_ID)) { + + LOG.info("Activating Contact..."); + + ContactId contactId = new ContactId( + localState.getLong(ADDED_CONTACT_ID).intValue()); + + // activate and show contact in contact list + db.setContactActive(txn, contactId, true); + + // broadcast event informing of successful introduction + Contact contact = db.getContact(txn, contactId); + Event event = new IntroductionSucceededEvent(contact); + txn.attach(event); + } else { + LOG.info( + "We must have had this contact already, not activating..."); + } + } + + // we need to abort the protocol, clean up what has been done + if (task == TASK_ABORT) { + if (localState.containsKey(ADDED_CONTACT_ID)) { + LOG.info("Deleting added contact due to abort..."); + ContactId contactId = new ContactId( + localState.getLong(ADDED_CONTACT_ID).intValue()); + contactManager.removeContact(contactId); + } + } + + } + + public void abort(Transaction txn, BdfDictionary state) { + + IntroduceeEngine engine = new IntroduceeEngine(); + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, TYPE_ABORT); + try { + processStateUpdate(txn, + engine.onLocalAction(state, localAction)); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private BdfDictionary encodeTransportProperties( + Map<TransportId, TransportProperties> map) { + + BdfDictionary d = new BdfDictionary(); + for (Map.Entry<TransportId, TransportProperties> e : map.entrySet()) { + d.put(e.getKey().getString(), e.getValue()); + } + return d; + } + + private Map<TransportId, TransportProperties> parseTransportProperties( + BdfDictionary d) throws FormatException { + + Map<TransportId, TransportProperties> tpMap = + new HashMap<TransportId, TransportProperties>(); + BdfDictionary tpMapDict = d.getDictionary(TRANSPORT); + for (String key : tpMapDict.keySet()) { + TransportId transportId = new TransportId(key); + TransportProperties transportProperties = new TransportProperties(); + BdfDictionary tpDict = tpMapDict.getDictionary(key); + for (String tkey : tpDict.keySet()) { + transportProperties.put(tkey, tpDict.getString(tkey)); + } + tpMap.put(transportId, transportProperties); + } + return tpMap; + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroducerEngine.java b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..fb88c28df61de2cb1d8bb1a1eedbd0e2cf62a14e --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroducerEngine.java @@ -0,0 +1,379 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.ProtocolEngine; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.IntroductionResponseReceivedEvent; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroducerAction; +import org.briarproject.api.introduction.IntroducerProtocolState; +import org.briarproject.api.introduction.IntroductionResponse; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.sync.MessageId; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.introduction.IntroducerAction.LOCAL_ABORT; +import static org.briarproject.api.introduction.IntroducerAction.LOCAL_REQUEST; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_1; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_2; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_1; +import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_2; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_ACKS; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_ACK_1; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_ACK_2; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_1; +import static org.briarproject.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_2; +import static org.briarproject.api.introduction.IntroducerProtocolState.ERROR; +import static org.briarproject.api.introduction.IntroducerProtocolState.FINISHED; +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_1; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_2; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1; +import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; +import static org.briarproject.api.introduction.IntroductionConstants.MSG; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +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.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; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +public class IntroducerEngine + implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { + + private static final Logger LOG = + Logger.getLogger(IntroducerEngine.class.getName()); + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( + BdfDictionary localState, BdfDictionary localAction) { + + try { + IntroducerProtocolState currentState = + getState(localState.getLong(STATE)); + int type = localAction.getLong(TYPE).intValue(); + IntroducerAction action = IntroducerAction.getLocal(type); + IntroducerProtocolState nextState = currentState.next(action); + + if (action == LOCAL_ABORT && currentState != ERROR) { + return abortSession(currentState, localState); + } + + if (nextState == ERROR) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Error: Invalid action in state " + + currentState.name()); + } + return noUpdate(localState); + } + + localState.put(STATE, nextState.getValue()); + 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(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY2)); + if (localAction.containsKey(MSG)) { + msg1.put(MSG, localAction.getString(MSG)); + } + messages.add(msg1); + 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(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY1)); + if (localAction.containsKey(MSG)) { + msg2.put(MSG, localAction.getString(MSG)); + } + messages.add(msg2); + logLocalAction(currentState, localState, msg2); + + List<Event> events = Collections.emptyList(); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } else { + throw new IllegalArgumentException("Unknown Local Action"); + } + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( + BdfDictionary localState, BdfDictionary msg) { + + try { + IntroducerProtocolState currentState = + getState(localState.getLong(STATE)); + int type = msg.getLong(TYPE).intValue(); + boolean one = isContact1(localState, msg); + IntroducerAction action = IntroducerAction.getRemote(type, one); + IntroducerProtocolState nextState = currentState.next(action); + + logMessageReceived(currentState, nextState, localState, type, msg); + + if (nextState == ERROR) { + if (currentState != ERROR) { + return abortSession(currentState, localState); + } else { + return noUpdate(localState); + } + } + + List<BdfDictionary> messages; + List<Event> events; + + // we have sent our requests and just got the 1st or 2nd response + if (currentState == AWAIT_RESPONSES || + currentState == AWAIT_RESPONSE_1 || + currentState == AWAIT_RESPONSE_2) { + // update next state based on message content + 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)); + + messages = forwardMessage(localState, msg); + events = Collections.singletonList(getEvent(localState, msg)); + } + // we have forwarded both responses and now received the 1st or 2nd ACK + else if (currentState == AWAIT_ACKS || + currentState == AWAIT_ACK_1 || + currentState == AWAIT_ACK_2) { + localState.put(STATE, nextState.getValue()); + messages = forwardMessage(localState, msg); + events = Collections.emptyList(); + } + // we probably received a response while already being FINISHED + 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)); + messages = Collections.emptyList(); + events = Collections.singletonList(getEvent(localState, msg)); + } else if (action == REMOTE_ACCEPT_2 || + action == REMOTE_DECLINE_2) { + localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID)); + messages = Collections.emptyList(); + 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); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + private void logLocalAction(IntroducerProtocolState state, + BdfDictionary localState, BdfDictionary msg) { + + if (!LOG.isLoggable(INFO)) return; + + try { + String to = getMessagePartner(localState, msg); + LOG.info("Sending introduction request in state " + state.name() + + " to " + to + " 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() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private void logMessageReceived(IntroducerProtocolState currentState, + IntroducerProtocolState nextState, + BdfDictionary localState, int type, BdfDictionary msg) { + if (!LOG.isLoggable(INFO)) return; + + try { + String t = "unknown"; + if (type == TYPE_REQUEST) t = "Introduction"; + else if (type == TYPE_RESPONSE) t = "Response"; + else if (type == TYPE_ACK) t = "ACK"; + else if (type == TYPE_ABORT) t = "Abort"; + + String from = getMessagePartner(localState, msg); + String to = getOtherContact(localState, msg); + + 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)) + ". " + + "Moving on to state " + nextState.name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private List<BdfDictionary> forwardMessage(BdfDictionary 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)); + } else { + msg.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); + } + + if (LOG.isLoggable(INFO)) { + LOG.info("Forwarding message to group " + + Arrays.hashCode(msg.getRaw(GROUP_ID))); + } + + return Collections.singletonList(msg); + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( + BdfDictionary localState, BdfDictionary delivered) { + try { + return noUpdate(localState); + } + 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(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, new byte[32])); // TODO remove byte[] + 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, new byte[32])); // TODO remove byte[] + } + + SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); + MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); + long time = msg.getLong(MESSAGE_TIME); + String name = getOtherContact(localState, msg); + boolean accept = msg.getBoolean(ACCEPT); + + IntroductionResponse ir = + new IntroductionResponse(sessionId, messageId, time, false, + false, false, false, authorId, name, accept); + return new IntroductionResponseReceivedEvent(contactId, ir); + } + + private boolean isContact1(BdfDictionary 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); + + if (Arrays.equals(group, group1)) { + return true; + } else if (Arrays.equals(group, group2)) { + return false; + } else { + throw new FormatException(); + } + } + + private String getMessagePartner(BdfDictionary 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); + } + return from; + } + + private String getOtherContact(BdfDictionary 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); + } + return to; + } + + private StateUpdate<BdfDictionary, BdfDictionary> abortSession( + IntroducerProtocolState currentState, BdfDictionary localState) + throws FormatException { + + if (LOG.isLoggable(WARNING)) { + LOG.warning("Aborting protocol session " + + Arrays.hashCode(localState.getRaw(SESSION_ID)) + + " in state " + currentState.name()); + } + + localState.put(STATE, ERROR.getValue()); + 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)); + 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)); + messages.add(msg2); + // TODO inform about protocol abort via new Event? + List<Event> events = Collections.emptyList(); + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } + + private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( + BdfDictionary localState) throws FormatException { + + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, new ArrayList<BdfDictionary>(0), + new ArrayList<Event>(0)); + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1b35fc26c0363c8ccbde45b93accb2cdf165e30a --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java @@ -0,0 +1,169 @@ +package org.briarproject.introduction; + +import org.briarproject.api.Bytes; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.Event; +import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; +import org.briarproject.util.StringUtils; + +import java.io.IOException; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS; +import static org.briarproject.api.introduction.IntroductionConstants.AUTHOR_ID_1; +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.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.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; + +class IntroducerManager { + + private static final Logger LOG = + Logger.getLogger(IntroducerManager.class.getName()); + + private final IntroductionManager introductionManager; + private final ClientHelper clientHelper; + private final Clock clock; + private final CryptoComponent cryptoComponent; + + IntroducerManager(IntroductionManager introductionManager, + ClientHelper clientHelper, Clock clock, + CryptoComponent cryptoComponent) { + + this.introductionManager = introductionManager; + this.clientHelper = clientHelper; + this.clock = clock; + this.cryptoComponent = cryptoComponent; + } + + public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2) + throws FormatException, DbException { + + // create local message to keep engine state + long now = clock.currentTimeMillis(); + Bytes salt = new Bytes(new byte[64]); + cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); + + Message m = clientHelper + .createMessage(introductionManager.getLocalGroup().getId(), now, + BdfList.of(salt)); + MessageId sessionId = m.getId(); + + Group g1 = introductionManager.getIntroductionGroup(c1); + Group g2 = introductionManager.getIntroductionGroup(c2); + + 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()); + + // save local state to database + clientHelper.addLocalMessage(txn, m, introductionManager.getClientId(), d, false); + + return d; + } + + public void makeIntroduction(Transaction txn, Contact c1, Contact c2, + String msg) 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); + + // define action + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, TYPE_REQUEST); + if (!StringUtils.isNullOrEmpty(msg)) { + localAction.put(MSG, msg); + } + localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey()); + localAction.put(PUBLIC_KEY2, c2.getAuthor().getPublicKey()); + + // start engine and process its state update + IntroducerEngine engine = new IntroducerEngine(); + processStateUpdate(txn, + engine.onLocalAction(localState, localAction)); + } + + public void incomingMessage(Transaction txn, BdfDictionary state, + BdfDictionary message) throws DbException, FormatException { + + IntroducerEngine engine = new IntroducerEngine(); + processStateUpdate(txn, + engine.onMessageReceived(state, message)); + } + + private void processStateUpdate(Transaction txn, + IntroducerEngine.StateUpdate<BdfDictionary, BdfDictionary> + result) throws DbException, FormatException { + + // save new local state + MessageId storageId = new MessageId(result.localState.getRaw(STORAGE_ID)); + clientHelper.mergeMessageMetadata(txn, storageId, result.localState); + + // send messages + for (BdfDictionary d : result.toSend) { + introductionManager.sendMessage(txn, d); + } + + // broadcast events + for (Event event : result.toBroadcast) { + txn.attach(event); + } + } + + public void abort(Transaction txn, BdfDictionary state) { + + IntroducerEngine engine = new IntroducerEngine(); + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, TYPE_ABORT); + try { + processStateUpdate(txn, + engine.onLocalAction(state, localAction)); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a01c62b1010fc5b6b964e4d8471faca613bb6a72 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -0,0 +1,509 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; +import org.briarproject.api.clients.PrivateGroupFactory; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.contact.ContactManager.AddContactHook; +import org.briarproject.api.contact.ContactManager.RemoveContactHook; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.data.MetadataParser; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.NoSuchMessageException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.introduction.IntroducerProtocolState; +import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.api.introduction.IntroductionMessage; +import org.briarproject.api.introduction.IntroductionRequest; +import org.briarproject.api.introduction.IntroductionResponse; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.properties.TransportPropertyManager; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupFactory; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.sync.MessageStatus; +import org.briarproject.api.system.Clock; +import org.briarproject.clients.BdfIncomingMessageHook; +import org.briarproject.util.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.introduction.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.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.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; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +class IntroductionManagerImpl extends BdfIncomingMessageHook + implements IntroductionManager, AddContactHook, RemoveContactHook { + + static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString( + "23b1897c198a90ae75b976ac023d0f32" + + "80ca67b12f2346b2c23a34f34e2434c3")); + + private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; + + private static final Logger LOG = + Logger.getLogger(IntroductionManagerImpl.class.getName()); + + private final DatabaseComponent db; + private final MessageQueueManager messageQueueManager; + private final PrivateGroupFactory privateGroupFactory; + private final MetadataEncoder metadataEncoder; + private final IntroducerManager introducerManager; + private final IntroduceeManager introduceeManager; + private final Group localGroup; + + @Inject + IntroductionManagerImpl(DatabaseComponent db, + MessageQueueManager messageQueueManager, + ClientHelper clientHelper, GroupFactory groupFactory, + PrivateGroupFactory privateGroupFactory, + MetadataEncoder metadataEncoder, MetadataParser metadataParser, + CryptoComponent cryptoComponent, + TransportPropertyManager transportPropertyManager, + AuthorFactory authorFactory, ContactManager contactManager, + Clock clock) { + + super(clientHelper, metadataParser, clock); + this.db = db; + this.messageQueueManager = messageQueueManager; + this.privateGroupFactory = privateGroupFactory; + this.metadataEncoder = metadataEncoder; + this.introducerManager = + new IntroducerManager(this, clientHelper, clock, + cryptoComponent); + this.introduceeManager = + new IntroduceeManager(db, this, clientHelper, clock, + cryptoComponent, transportPropertyManager, + authorFactory, contactManager); + localGroup = + groupFactory.createGroup(CLIENT_ID, LOCAL_GROUP_DESCRIPTOR); + } + + @Override + public ClientId getClientId() { + return CLIENT_ID; + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + try { + // create an introduction group for sending introduction messages + Group g = getIntroductionGroup(c); + db.addGroup(txn, g); + db.setVisibleToContact(txn, c.getId(), g.getId(), true); + // Attach the contact ID to the group + BdfDictionary gm = new BdfDictionary(); + gm.put(CONTACT, c.getId().getInt()); + clientHelper.mergeGroupMetadata(txn, g.getId(), gm); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + // check for open sessions with that contact and abort those + Long id = (long) c.getId().getInt(); + try { + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) { + BdfDictionary d = entry.getValue(); + long role = d.getLong(ROLE, -1L); + if (role != ROLE_INTRODUCER) continue; + if (d.getLong(CONTACT_ID_1).equals(id) || + d.getLong(CONTACT_ID_2).equals(id)) { + + IntroducerProtocolState state = IntroducerProtocolState + .fromValue(d.getLong(STATE).intValue()); + if (IntroducerProtocolState.isOngoing(state)) { + introducerManager.abort(txn, d); + } + } + + } + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + + // remove the group (all messages will be removed with it) + // this contact won't get our abort message, but the other will + db.removeGroup(txn, getIntroductionGroup(c)); + } + + /** + * This is called when a new message arrived and is being validated. + * It is the central method where we determine which role we play + * in the introduction protocol and which engine we need to start. + */ + @Override + protected void incomingMessage(Transaction txn, Message m, BdfList body, + BdfDictionary message) throws DbException { + + // add local group for engine states to make sure it exists + db.addGroup(txn, localGroup); + + // Get message data and type + GroupId groupId = m.getGroupId(); + message.put(GROUP_ID, groupId); + long type = message.getLong(TYPE, -1L); + + // we are an introducee, need to initialize new state + if (type == TYPE_REQUEST) { + BdfDictionary state; + try { + state = introduceeManager.initialize(txn, groupId, message); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Could not initialize introducee state"); + LOG.log(WARNING, e.toString(), e); + } + return; + } + try { + introduceeManager.incomingMessage(txn, state, message); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + introduceeManager.abort(txn, state); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + introduceeManager.abort(txn, state); + } + } + // our role can be anything + else if (type == TYPE_RESPONSE || type == TYPE_ACK || type == TYPE_ABORT) { + BdfDictionary state; + try { + state = getSessionState(txn, + message.getRaw(SESSION_ID, new byte[0])); + } 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); + } else { + if(LOG.isLoggable(WARNING)) { + LOG.warning("Unknown role '" + role + + "'. Deleting message..."); + deleteMessage(txn, m.getId()); + } + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state); + else introduceeManager.abort(txn, state); + } 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); + } + } else { + // the message has been validated, so this should not happen + if(LOG.isLoggable(WARNING)) { + LOG.warning("Unknown message type '" + type + "', deleting..."); + } + } + } + + @Override + public void makeIntroduction(Contact c1, Contact c2, String msg) + throws DbException, FormatException { + + Transaction txn = db.startTransaction(false); + try { + // add local group for session states to make sure it exists + db.addGroup(txn, getLocalGroup()); + introducerManager.makeIntroduction(txn, c1, c2, msg); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + } + + @Override + public void acceptIntroduction(final SessionId sessionId) + throws DbException, FormatException { + + Transaction txn = db.startTransaction(false); + try { + introduceeManager.acceptIntroduction(txn, sessionId); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + } + + @Override + public void declineIntroduction(final SessionId sessionId) + throws DbException, FormatException { + + Transaction txn = db.startTransaction(false); + try { + introduceeManager.declineIntroduction(txn, sessionId); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + } + + @Override + public Collection<IntroductionMessage> getIntroductionMessages( + ContactId contactId) throws DbException { + + Collection<IntroductionMessage> list = + new ArrayList<IntroductionMessage>(); + + Map<MessageId, BdfDictionary> metadata; + Collection<MessageStatus> statuses; + Transaction txn = db.startTransaction(true); + try { + // get messages and their status + GroupId g = + getIntroductionGroup(db.getContact(txn, contactId)).getId(); + metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); + statuses = db.getMessageStatus(txn, contactId, g); + + // turn messages into classes for the UI + Map<SessionId, BdfDictionary> sessionStates = + new HashMap<SessionId, BdfDictionary>(); + for (MessageStatus s : statuses) { + MessageId messageId = s.getMessageId(); + BdfDictionary msg = metadata.get(messageId); + if (msg == null) continue; + + try { + long type = msg.getLong(TYPE); + if (type == TYPE_ACK || type == TYPE_ABORT) continue; + + // get session state + SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); + BdfDictionary state = sessionStates.get(sessionId); + if (state == null) { + state = getSessionState(txn, sessionId.getBytes()); + } + sessionStates.put(sessionId, state); + + boolean local; + long time = msg.getLong(MESSAGE_TIME); + boolean accepted = msg.getBoolean(ACCEPT, false); + boolean read = msg.getBoolean(READ, false); + AuthorId authorId; + String name; + if (type == TYPE_RESPONSE) { + if (state.getLong(ROLE) == ROLE_INTRODUCER) { + if (!concernsThisContact(contactId, messageId, state)) { + // this response is not from contactId + continue; + } + local = false; + authorId = + getAuthorIdForIntroducer(contactId, state); + name = getNameForIntroducer(contactId, state); + } else { + if (Arrays.equals(state.getRaw(NOT_OUR_RESPONSE), + messageId.getBytes())) { + // this response is not ours, don't include it + continue; + } + local = true; + authorId = new AuthorId( + state.getRaw(REMOTE_AUTHOR_ID)); + name = state.getString(NAME); + } + IntroductionResponse ir = new IntroductionResponse( + sessionId, messageId, time, local, s.isSent(), + s.isSeen(), read, authorId, name, accepted); + list.add(ir); + } else if (type == TYPE_REQUEST) { + String message; + boolean answered, exists; + if (state.getLong(ROLE) == ROLE_INTRODUCER) { + local = true; + authorId = + getAuthorIdForIntroducer(contactId, state); + name = getNameForIntroducer(contactId, state); + message = msg.getOptionalString(MSG); + answered = false; + exists = false; + } else { + local = false; + authorId = new AuthorId( + state.getRaw(REMOTE_AUTHOR_ID)); + name = state.getString(NAME); + message = state.getOptionalString(MSG); + answered = state.getBoolean(ANSWERED); + exists = state.getBoolean(EXISTS); + } + IntroductionRequest ir = new IntroductionRequest( + sessionId, messageId, time, local, s.isSent(), + s.isSeen(), read, authorId, name, accepted, + message, answered, exists); + list.add(ir); + } + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + return list; + } + + private String getNameForIntroducer(ContactId contactId, + BdfDictionary 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); + throw new RuntimeException("Contact not part of this introduction session"); + } + + private AuthorId getAuthorIdForIntroducer(ContactId contactId, + BdfDictionary 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)); + throw new RuntimeException("Contact not part of this introduction session"); + } + + private boolean concernsThisContact(ContactId contactId, MessageId messageId, + BdfDictionary state) throws FormatException { + + if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) { + return Arrays.equals(state.getRaw(RESPONSE_1, new byte[0]), + messageId.getBytes()); + } else { + return Arrays.equals(state.getRaw(RESPONSE_2, new byte[0]), + messageId.getBytes()); + } + } + + @Override + public void setReadFlag(MessageId m, boolean read) throws DbException { + try { + BdfDictionary meta = BdfDictionary.of(new BdfEntry(READ, read)); + clientHelper.mergeMessageMetadata(m, meta); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + + public BdfDictionary getSessionState(Transaction txn, byte[] sessionId) + throws DbException, FormatException { + + try { + return clientHelper.getMessageMetadataAsDictionary(txn, + new MessageId(sessionId)); + } catch (NoSuchMessageException e) { + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, + localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) { + return m.getValue(); + } + } + if (LOG.isLoggable(WARNING)) { + LOG.warning( + "No session state found for this message with session ID " + + Arrays.hashCode(sessionId)); + } + throw new FormatException(); + } + } + + public Group getIntroductionGroup(Contact c) { + return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); + } + + public Group getLocalGroup() { + return localGroup; + } + + public void sendMessage(Transaction txn, BdfDictionary message) + throws DbException, FormatException { + + BdfList bdfList = MessageEncoder.encodeMessage(message); + byte[] body = clientHelper.toByteArray(bdfList); + GroupId groupId = new GroupId(message.getRaw(GROUP_ID)); + Group group = db.getGroup(txn, groupId); + long timestamp = System.currentTimeMillis(); + + message.put(MESSAGE_TIME, timestamp); + Metadata metadata = metadataEncoder.encode(message); + + messageQueueManager + .sendMessage(txn, group, timestamp, body, metadata); + } + + private void deleteMessage(Transaction txn, MessageId messageId) + throws DbException { + + db.deleteMessage(txn, messageId); + db.deleteMessageMetadata(txn, messageId); + } + +} diff --git a/briar-core/src/org/briarproject/introduction/IntroductionModule.java b/briar-core/src/org/briarproject/introduction/IntroductionModule.java new file mode 100644 index 0000000000000000000000000000000000000000..686a34edadb0d4451cc4162bcf79bac33345581b --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroductionModule.java @@ -0,0 +1,56 @@ +package org.briarproject.introduction; + +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.api.system.Clock; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class IntroductionModule { + + public static class EagerSingletons { + @Inject IntroductionManager introductionManager; + @Inject IntroductionValidator introductionValidator; + } + + @Provides + @Singleton + IntroductionValidator getValidator(MessageQueueManager messageQueueManager, + IntroductionManager introductionManager, + MetadataEncoder metadataEncoder, ClientHelper clientHelper, + Clock clock) { + + IntroductionValidator introductionValidator = new IntroductionValidator( + clientHelper, metadataEncoder, clock); + + messageQueueManager.registerMessageValidator( + introductionManager.getClientId(), + introductionValidator); + + return introductionValidator; + } + + @Provides + @Singleton + IntroductionManager getIntroductionManager( + ContactManager contactManager, + MessageQueueManager messageQueueManager, + IntroductionManagerImpl introductionManager) { + + contactManager.registerAddContactHook(introductionManager); + contactManager.registerRemoveContactHook(introductionManager); + messageQueueManager + .registerIncomingMessageHook(introductionManager.getClientId(), + introductionManager); + + return introductionManager; + } +} diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..addf58ea49749522873ff662e506e42055d42dfa --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java @@ -0,0 +1,173 @@ +package org.briarproject.introduction; + +import org.briarproject.api.DeviceId; +import org.briarproject.api.FormatException; +import org.briarproject.api.TransportId; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.introduction.SessionId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Message; +import org.briarproject.api.system.Clock; +import org.briarproject.clients.BdfMessageValidator; + +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_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.MSG; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; +import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; + +class IntroductionValidator extends BdfMessageValidator { + + IntroductionValidator(ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + super(clientHelper, metadataEncoder, clock); + } + + @Override + protected BdfDictionary validateMessage(Message m, Group g, BdfList body) + throws FormatException { + + BdfDictionary d; + long type = body.getLong(0); + byte[] id = body.getRaw(1); + checkLength(id, SessionId.LENGTH); + + if (type == TYPE_REQUEST) { + d = validateRequest(body); + } else if (type == TYPE_RESPONSE) { + d = validateResponse(body); + } else if (type == TYPE_ACK) { + d = validateAck(body); + } else if (type == TYPE_ABORT) { + d = validateAbort(body); + } else { + throw new FormatException(); + } + + d.put(TYPE, type); + d.put(SESSION_ID, id); + d.put(MESSAGE_ID, m.getId()); + d.put(MESSAGE_TIME, m.getTimestamp()); + return d; + } + + private BdfDictionary validateRequest(BdfList message) + throws FormatException { + + checkSize(message, 4, 5); + + // parse contact name + String name = message.getString(2); + checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); + + // parse contact's public key + byte[] key = message.getRaw(3); + checkLength(key, 0, MAX_PUBLIC_KEY_LENGTH); + + // parse (optional) message + String msg = null; + if (message.size() == 5) { + msg = message.getString(4); + checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH); + } + + // Return the metadata + BdfDictionary d = new BdfDictionary(); + d.put(NAME, name); + d.put(PUBLIC_KEY, key); + if (msg != null) { + d.put(MSG, msg); + } + return d; + } + + private BdfDictionary validateResponse(BdfList message) + throws FormatException { + + checkSize(message, 3, 7); + + // parse accept/decline + boolean accept = message.getBoolean(2); + + long time = 0; + byte[] pubkey = null; + byte[] deviceId = null; + BdfDictionary tp = new BdfDictionary(); + if (accept) { + checkSize(message, 7); + + // parse timestamp + time = message.getLong(3); + + // parse ephemeral public key + pubkey = message.getRaw(4); + checkLength(pubkey, 0, MAX_PUBLIC_KEY_LENGTH); + + // parse device ID + deviceId = message.getRaw(5); + checkLength(deviceId, DeviceId.LENGTH); + + // parse transport properties + tp = message.getDictionary(6); + if (tp.size() < 1) throw new FormatException(); + for (String tId : tp.keySet()) { + checkLength(tId, 1, TransportId.MAX_TRANSPORT_ID_LENGTH); + BdfDictionary tProps = tp.getDictionary(tId); + for (String propId : tProps.keySet()) { + checkLength(propId, 0, MAX_PROPERTY_LENGTH); + String prop = tProps.getString(propId); + checkLength(prop, 0, MAX_PROPERTY_LENGTH); + } + } + } else { + checkSize(message, 3); + } + + // Return the metadata + BdfDictionary d = new BdfDictionary(); + d.put(ACCEPT, accept); + if (accept) { + d.put(TIME, time); + d.put(E_PUBLIC_KEY, pubkey); + d.put(DEVICE_ID, deviceId); + d.put(TRANSPORT, tp); + } + return d; + } + + private BdfDictionary validateAck(BdfList message) + throws FormatException { + + checkSize(message, 2); + + // Return the metadata + return new BdfDictionary(); + } + + private BdfDictionary validateAbort(BdfList message) + throws FormatException { + + checkSize(message, 2); + + // Return the metadata + return new BdfDictionary(); + } +} diff --git a/briar-core/src/org/briarproject/introduction/MessageEncoder.java b/briar-core/src/org/briarproject/introduction/MessageEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..87bf4d5e6a4e63a0a5d35f793d7e30b653020437 --- /dev/null +++ b/briar-core/src/org/briarproject/introduction/MessageEncoder.java @@ -0,0 +1,74 @@ +package org.briarproject.introduction; + +import org.briarproject.api.FormatException; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; + +import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT; +import static org.briarproject.api.introduction.IntroductionConstants.DEVICE_ID; +import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.MSG; +import static org.briarproject.api.introduction.IntroductionConstants.NAME; +import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; +import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.api.introduction.IntroductionConstants.TIME; +import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; + +public class MessageEncoder { + + public static BdfList encodeMessage(BdfDictionary d) throws FormatException { + + BdfList body; + long type = d.getLong(TYPE); + if (type == TYPE_REQUEST) { + body = encodeRequest(d); + } else if (type == TYPE_RESPONSE) { + body = encodeResponse(d); + } else if (type == TYPE_ACK) { + body = encodeAck(d); + } else if (type == TYPE_ABORT) { + body = encodeAbort(d); + } else { + throw new FormatException(); + } + return body; + } + + private static BdfList encodeRequest(BdfDictionary d) throws FormatException { + BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID), + d.getString(NAME), d.getRaw(PUBLIC_KEY)); + + if (d.containsKey(MSG)) { + list.add(d.getString(MSG)); + } + return list; + } + + private static BdfList encodeResponse(BdfDictionary d) throws FormatException { + BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID), + d.getBoolean(ACCEPT)); + + if (d.getBoolean(ACCEPT)) { + list.add(d.getLong(TIME)); + list.add(d.getRaw(E_PUBLIC_KEY)); + list.add(d.getRaw(DEVICE_ID)); + list.add(d.getDictionary(TRANSPORT)); + } + // TODO Sign the response, see #256 + return list; + } + + private static BdfList encodeAck(BdfDictionary d) throws FormatException { + return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID)); + } + + private static BdfList encodeAbort(BdfDictionary d) throws FormatException { + return BdfList.of(TYPE_ABORT, d.getRaw(SESSION_ID)); + } + +} diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java index 9910ff42fe9facb2d1a0a5eebebff2cc89b1f6da..4b10c48edeef1b79ea322826b2e3661bd3d7f32b 100644 --- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java +++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java @@ -82,18 +82,12 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, } @Override - public void addRemoteProperties(ContactId c, DeviceId dev, + public void addRemoteProperties(Transaction txn, ContactId c, DeviceId dev, Map<TransportId, TransportProperties> props) throws DbException { - Transaction txn = db.startTransaction(false); - try { - Group g = getContactGroup(db.getContact(txn, c)); - for (Entry<TransportId, TransportProperties> e : props.entrySet()) { - storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0, - false, false); - } - txn.setComplete(); - } finally { - db.endTransaction(txn); + Group g = getContactGroup(db.getContact(txn, c)); + for (Entry<TransportId, TransportProperties> e : props.entrySet()) { + storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0, + false, false); } }