From 021861f4c1cba9e14b034fb729fad0afd4d21c85 Mon Sep 17 00:00:00 2001
From: goapunk <goapunk@riseup.net>
Date: Sat, 22 Sep 2018 13:30:04 +0200
Subject: [PATCH] temp

---
 .../TransportPropertyManagerImpl.java         |   1 +
 ...ilboxIntroductionRequestReceivedEvent.java |  24 ++
 ...lboxIntroductionResponseReceivedEvent.java |  29 ++
 .../introduction/IntroductionManagerImpl.java |   1 +
 .../briar/mailbox/AbortMessage.java           |   5 +-
 .../AbstractMailboxIntroductionMessage.java   |   5 +-
 .../briar/mailbox/AbstractProtocolEngine.java |  69 +++--
 .../briar/mailbox/ContactAcceptMessage.java   |   5 +-
 .../briar/mailbox/ContactRequestMessage.java  |   2 +-
 .../briar/mailbox/DeclineMessage.java         |   4 +-
 .../briar/mailbox/IntroductionConstants.java  |   2 +-
 .../briar/mailbox/MailboxAcceptMessage.java   |  16 +-
 .../briar/mailbox/MailboxAuthMessage.java     |   5 +-
 .../MailboxIntroductionManagerImpl.java       | 248 ++++++++++++++++--
 .../mailbox/MailboxIntroductionModule.java    |  20 +-
 .../mailbox/MailboxIntroductionValidator.java |  33 +--
 ...ncoder.java => MailboxMessageEncoder.java} |  19 +-
 ...pl.java => MailboxMessageEncoderImpl.java} |  67 ++---
 ...eParser.java => MailboxMessageParser.java} |   9 +-
 ...mpl.java => MailboxMessageParserImpl.java} |  56 ++--
 .../briar/mailbox/MailboxProtocolEngine.java  | 182 +++++++++++++
 .../briar/mailbox/MailboxSession.java         |  44 ++--
 ...ncoder.java => MailboxSessionEncoder.java} |   2 +-
 ...pl.java => MailboxSessionEncoderImpl.java} |   6 +-
 ...nParser.java => MailboxSessionParser.java} |   4 +-
 ...mpl.java => MailboxSessionParserImpl.java} |  37 +--
 .../briar/mailbox/MessageMetadata.java        |  55 ++++
 .../briar/mailbox/OwnerProtocolEngine.java    |  65 +++--
 .../briar/mailbox/OwnerSession.java           |   3 +-
 .../briar/mailbox/RequestMessage.java         |  13 +-
 .../briarproject/briar/mailbox/Session.java   |  14 +-
 .../briar/messaging/MessagingManagerImpl.java |   1 +
 .../GroupInvitationManagerImpl.java           |   2 +
 .../briar/sharing/SharingManagerImpl.java     |   2 +
 .../IntroductionIntegrationTest.java          |   4 +
 .../MailboxIntroductionIntegrationTest.java   | 105 ++++----
 ...xIntroductionIntegrationTestComponent.java |   6 +-
 .../briar/test/BriarIntegrationTest.java      |   2 +-
 38 files changed, 815 insertions(+), 352 deletions(-)
 create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionRequestReceivedEvent.java
 create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionResponseReceivedEvent.java
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{MessageEncoder.java => MailboxMessageEncoder.java} (71%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{MessageEncoderImpl.java => MailboxMessageEncoderImpl.java} (72%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{MessageParser.java => MailboxMessageParser.java} (85%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{MessageParserImpl.java => MailboxMessageParserImpl.java} (74%)
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{SessionEncoder.java => MailboxSessionEncoder.java} (93%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{SessionEncoderImpl.java => MailboxSessionEncoderImpl.java} (98%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{SessionParser.java => MailboxSessionParser.java} (87%)
 rename briar-core/src/main/java/org/briarproject/briar/mailbox/{SessionParserImpl.java => MailboxSessionParserImpl.java} (89%)
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java

diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java
index 98822d8b0..4b058a6da 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java
@@ -108,6 +108,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility v) throws DbException {
+		if(!getApplicableContactTypes().contains(c.getType())) return;
 		// Apply the client's visibility to the contact group
 		Group g = getContactGroup(c);
 		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionRequestReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionRequestReceivedEvent.java
new file mode 100644
index 000000000..40eb88813
--- /dev/null
+++ b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionRequestReceivedEvent.java
@@ -0,0 +1,24 @@
+package org.briarproject.briar.api.mailbox.event;
+
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.identity.AuthorId;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+public class MailboxIntroductionRequestReceivedEvent extends Event {
+
+	private final AuthorId contactId;
+
+	public MailboxIntroductionRequestReceivedEvent(AuthorId contactId) {
+		this.contactId = contactId;
+
+	}
+
+	public AuthorId getAuthorId() {
+		return contactId;
+	}
+
+}
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionResponseReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionResponseReceivedEvent.java
new file mode 100644
index 000000000..c4bba8eb1
--- /dev/null
+++ b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionResponseReceivedEvent.java
@@ -0,0 +1,29 @@
+package org.briarproject.briar.api.mailbox.event;
+
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+public class MailboxIntroductionResponseReceivedEvent extends Event {
+
+	private final Author from;
+	private final Author to;
+
+	public MailboxIntroductionResponseReceivedEvent(Author from, Author to) {
+		this.from = from;
+		this.to = to;
+	}
+
+
+	public Author getFrom() {
+		return from;
+	}
+
+	public Author getTo() {
+		return to;
+	}
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
index 19021d113..d387af92c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
@@ -155,6 +155,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility v) throws DbException {
+		if(!getApplicableContactTypes().contains(c.getType())) return;
 		// Apply the client's visibility to the contact group
 		Group g = getContactGroup(c);
 		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java
index 413705a17..eb326edb4 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java
@@ -15,9 +15,8 @@ class AbortMessage extends AbstractMailboxIntroductionMessage {
 	private final SessionId sessionId;
 
 	protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			@Nullable MessageId previousMessageId, SessionId sessionId) {
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java
index fd8419d72..376480baf 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java
@@ -16,16 +16,13 @@ abstract class AbstractMailboxIntroductionMessage {
 	private final long timestamp;
 	@Nullable
 	private final MessageId previousMessageId;
-	private final long messageCounter;
 
 	AbstractMailboxIntroductionMessage(MessageId messageId, GroupId groupId,
-			long timestamp, @Nullable MessageId previousMessageId,
-			long messageCounter) {
+			long timestamp, @Nullable MessageId previousMessageId) {
 		this.messageId = messageId;
 		this.groupId = groupId;
 		this.timestamp = timestamp;
 		this.previousMessageId = previousMessageId;
-		this.messageCounter = messageCounter;
 	}
 
 	MessageId getMessageId() {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
index ad3cd17b2..25bc73371 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
@@ -11,16 +11,12 @@ import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.SessionId;
 
-import java.util.Map;
-
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
@@ -41,20 +37,18 @@ abstract class AbstractProtocolEngine<S extends Session>
 	protected final ContactGroupFactory contactGroupFactory;
 	protected final MessageTracker messageTracker;
 	protected final IdentityManager identityManager;
-	protected final MessageParser messageParser;
-	protected final MessageEncoder messageEncoder;
+	protected final MailboxMessageParser messageParser;
+	protected final MailboxMessageEncoder messageEncoder;
 	protected final Clock clock;
+	protected final MailboxIntroductionCrypto crypto;
 
-	AbstractProtocolEngine(
-			DatabaseComponent db,
-			ClientHelper clientHelper,
+	AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
 			ContactManager contactManager,
 			ContactGroupFactory contactGroupFactory,
-			MessageTracker messageTracker,
-			IdentityManager identityManager,
-			MessageParser messageParser,
-			MessageEncoder messageEncoder,
-			Clock clock) {
+			MessageTracker messageTracker, IdentityManager identityManager,
+			MailboxMessageParser messageParser,
+			MailboxMessageEncoder messageEncoder, Clock clock,
+			MailboxIntroductionCrypto crypto) {
 		this.db = db;
 		this.clientHelper = clientHelper;
 		this.contactManager = contactManager;
@@ -64,38 +58,39 @@ abstract class AbstractProtocolEngine<S extends Session>
 		this.messageParser = messageParser;
 		this.messageEncoder = messageEncoder;
 		this.clock = clock;
+		this.crypto = crypto;
 	}
 
 	Message sendMailboxRequestMessage(Transaction txn, PeerSession s,
-			long timestamp) throws DbException {
+			long timestamp, Author introduceeAuthor, long messageCounter)
+			throws DbException {
 		Message m = messageEncoder
 				.encodeRequestMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId());
-		sendMessage(txn, MAILBOX_REQUEST, s.getSessionId(), m);
+						s.getLastLocalMessageId(), introduceeAuthor,
+						messageCounter);
+		sendMessage(txn, MAILBOX_REQUEST, s.getSessionId(), m, messageCounter);
 		return m;
 	}
 
 	Message sendMailboxAcceptMessage(Transaction txn, PeerSession s,
-			long timestamp,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties,
-			boolean visible) throws DbException {
+			long timestamp, byte[] ephemeralPublicKey, long acceptTimestamp,
+			long messageCounter) throws DbException {
 		Message m = messageEncoder
 				.encodeMailboxAcceptMessage(s.getContactGroupId(), timestamp,
 						s.getLastLocalMessageId(), s.getSessionId(),
-						ephemeralPublicKey, acceptTimestamp,
-						transportProperties);
-		sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m);
+						ephemeralPublicKey, acceptTimestamp, messageCounter);
+		sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m, messageCounter);
 		return m;
 	}
 
 	Message sendContactRequestMessage(Transaction txn, PeerSession s,
-			long timestamp, Author author, @Nullable String message)
-			throws DbException {
+			long timestamp, Author author, Author introduceeAuthor,
+			long messageCounter) throws DbException {
 		Message m = messageEncoder
 				.encodeRequestMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId());
-		sendMessage(txn, CONTACT_REQUEST, s.getSessionId(), m);
+						s.getLastLocalMessageId(), introduceeAuthor,
+						messageCounter);
+		sendMessage(txn, CONTACT_REQUEST, s.getSessionId(), m, 0);
 		return m;
 	}
 
@@ -136,7 +131,7 @@ abstract class AbstractProtocolEngine<S extends Session>
 		Message m = messageEncoder
 				.encodeDeclineMessage(s.getContactGroupId(), timestamp,
 						s.getLastLocalMessageId(), s.getSessionId());
-		sendMessage(txn, DECLINE, s.getSessionId(), m);
+		sendMessage(txn, DECLINE, s.getSessionId(), m, 0);
 		return m;
 	}
 
@@ -145,15 +140,16 @@ abstract class AbstractProtocolEngine<S extends Session>
 		Message m = messageEncoder
 				.encodeAbortMessage(s.getContactGroupId(), timestamp,
 						s.getLastLocalMessageId(), s.getSessionId());
-		sendMessage(txn, ABORT, s.getSessionId(), m);
+		sendMessage(txn, ABORT, s.getSessionId(), m, 0);
 		return m;
 	}
 
 	private void sendMessage(Transaction txn, MessageType type,
-			SessionId sessionId, Message m)
+			SessionId sessionId, Message m, long messageCounter)
 			throws DbException {
 		BdfDictionary meta = messageEncoder
-				.encodeMetadata(type, sessionId, m.getTimestamp(), true);
+				.encodeMetadata(type, sessionId, m.getTimestamp(), true,
+						messageCounter);
 		try {
 			clientHelper.addLocalMessage(txn, m, meta, true);
 		} catch (FormatException e) {
@@ -169,13 +165,8 @@ abstract class AbstractProtocolEngine<S extends Session>
 	}
 
 	long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
-		return Math.max(
-				clock.currentTimeMillis(),
-				Math.max(
-						localTimestamp,
-						requestTimestamp
-				) + 1
-		);
+		return Math.max(clock.currentTimeMillis(),
+				Math.max(localTimestamp, requestTimestamp) + 1);
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java
index 82781be3d..8797283b4 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java
@@ -21,9 +21,8 @@ class ContactAcceptMessage extends AbstractMailboxIntroductionMessage {
 	protected ContactAcceptMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
 			SessionId sessionId, byte[] ephemeralPublicKey,
-			long acceptTimestamp, byte[] mac, byte[] signature,
-			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			long acceptTimestamp, byte[] mac, byte[] signature) {
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 		this.ephemeralPublicKey = ephemeralPublicKey;
 		this.acceptTimestamp = acceptTimestamp;
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java
index ecfa53859..676ee4500 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java
@@ -21,7 +21,7 @@ class ContactRequestMessage extends AbstractMailboxIntroductionMessage {
 			SessionId sessionId, byte[] ephemeralPublicKey,
 			long acceptTimestamp,
 			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 		this.ephemeralPublicKey = ephemeralPublicKey;
 		this.acceptTimestamp = acceptTimestamp;
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java
index df841bdcd..b7da465ea 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java
@@ -16,8 +16,8 @@ class DeclineMessage extends AbstractMailboxIntroductionMessage {
 
 	protected DeclineMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
-			SessionId sessionId, long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			SessionId sessionId) {
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java
index fa6bc0624..3e2d61274 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java
@@ -10,7 +10,7 @@ interface IntroductionConstants {
 	String MSG_KEY_SESSION_ID = "sessionId";
 	String MSG_KEY_TIMESTAMP = "timestamp";
 	String MSG_KEY_LOCAL = "local";
-	String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";
+	String MSG_KEY_COUNTER = "counter";
 	String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer";
 
 	// Session Keys
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java
index 7284e0273..6b740de82 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java
@@ -1,14 +1,10 @@
 package org.briarproject.briar.mailbox;
 
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.SessionId;
 
-import java.util.Map;
-
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
@@ -19,19 +15,15 @@ class MailboxAcceptMessage extends AbstractMailboxIntroductionMessage {
 	private final SessionId sessionId;
 	private final byte[] ephemeralPublicKey;
 	private final long acceptTimestamp;
-	private final Map<TransportId, TransportProperties> transportProperties;
 
 	protected MailboxAcceptMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
 			SessionId sessionId, byte[] ephemeralPublicKey,
-			long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties,
-			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			long acceptTimestamp) {
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 		this.ephemeralPublicKey = ephemeralPublicKey;
 		this.acceptTimestamp = acceptTimestamp;
-		this.transportProperties = transportProperties;
 	}
 
 	public SessionId getSessionId() {
@@ -46,8 +38,4 @@ class MailboxAcceptMessage extends AbstractMailboxIntroductionMessage {
 		return acceptTimestamp;
 	}
 
-	public Map<TransportId, TransportProperties> getTransportProperties() {
-		return transportProperties;
-	}
-
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java
index 917a841ad..da930637d 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java
@@ -28,9 +28,8 @@ class MailboxAuthMessage extends AbstractMailboxIntroductionMessage {
 			SessionId sessionId, byte[] ephemeralPublicKey,
 			long acceptTimestamp,
 			Map<TransportId, TransportProperties> transportProperties,
-			byte[] mac, byte[] signature,
-			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			byte[] mac, byte[] signature) {
+		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 		this.ephemeralPublicKey = ephemeralPublicKey;
 		this.acceptTimestamp = acceptTimestamp;
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
index 69258f5ad..82a802fee 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
@@ -4,7 +4,9 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.client.ContactGroupFactory;
 import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.contact.ContactType;
 import org.briarproject.bramble.api.contact.PrivateMailbox;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
@@ -23,8 +25,11 @@ import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.versioning.ClientVersioningManager;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.mailbox.MailboxIntroductionManager;
+import org.briarproject.briar.api.mailbox.Role;
 import org.briarproject.briar.client.BdfIncomingMessageHook;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import java.util.logging.Logger;
 
@@ -32,9 +37,13 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.contact.ContactManager.ContactHook;
+import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER;
+import static org.briarproject.bramble.api.contact.ContactType.values;
 import static org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
 import static org.briarproject.briar.api.mailbox.Role.MAILBOX;
 import static org.briarproject.briar.api.mailbox.Role.OWNER;
+import static org.briarproject.briar.mailbox.IntroductionConstants.GROUP_KEY_CONTACT_ID;
+import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 
 class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 		implements MailboxIntroductionManager, Client, ClientVersioningHook,
@@ -45,31 +54,29 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 	private final ClientVersioningManager clientVersioningManager;
 	private final ContactGroupFactory contactGroupFactory;
 	private final ContactManager contactManager;
-	private final MessageParser messageParser;
+	private final MailboxMessageParser messageParser;
 	//	private final SessionEncoder sessionEncoder;
-	private final SessionParser sessionParser;
+	private final MailboxSessionParser sessionParser;
 	private final OwnerProtocolEngine ownerProtocolEngine;
-	private final SessionEncoder sessionEncoder;
+	private final MailboxSessionEncoder sessionEncoder;
 	private final MailboxIntroductionCrypto crypto;
-	//private final MailboxProtocolEngine mailboxProtocolEngine;
+	private final MailboxProtocolEngine mailboxProtocolEngine;
 	private final IdentityManager identityManager;
 
 	private final Group localGroup;
 
 	@Inject
-	MailboxIntroductionManagerImpl(
-			DatabaseComponent db,
+	MailboxIntroductionManagerImpl(DatabaseComponent db,
 			ClientHelper clientHelper,
 			ClientVersioningManager clientVersioningManager,
 			MetadataParser metadataParser,
 			ContactGroupFactory contactGroupFactory,
-			ContactManager contactManager,
-			MessageParser messageParser,
-			SessionParser sessionParser,
+			ContactManager contactManager, MailboxMessageParser messageParser,
+			MailboxSessionParser sessionParser,
 			OwnerProtocolEngine ownerProtocolEngine,
-			SessionEncoder sessionEncoder,
+			MailboxSessionEncoder sessionEncoder,
 			MailboxIntroductionCrypto crypto,
-		//	MailboxProtocolEngine mailboxProtocolEngine,
+			MailboxProtocolEngine mailboxProtocolEngine,
 			IdentityManager identityManager) {
 		super(db, clientHelper, metadataParser);
 		this.clientVersioningManager = clientVersioningManager;
@@ -80,24 +87,194 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 		this.ownerProtocolEngine = ownerProtocolEngine;
 		this.sessionEncoder = sessionEncoder;
 		this.crypto = crypto;
-//		this.mailboxProtocolEngine = mailboxProtocolEngine;
+		this.mailboxProtocolEngine = mailboxProtocolEngine;
 		this.identityManager = identityManager;
 		this.localGroup =
 				contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION);
 	}
 
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		// Create a local group to store protocol sessions
+		if (db.containsGroup(txn, localGroup.getId())) return;
+		db.addGroup(txn, localGroup);
+		// Set up groups for communication with any pre-existing contacts
+		for (Contact c : db.getAllContacts(txn)) addingContact(txn, c);
+	}
+
 	@Override
 	protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
-			BdfDictionary meta) throws DbException, FormatException {
+			BdfDictionary bdfMeta) throws DbException, FormatException {
+		// For testing
+		ContactId introduceeId = getContactId(txn, m.getGroupId());
+		ContactType type = db.getContactType(txn, introduceeId);
+		if (type == MAILBOX_OWNER) {
+			return incomingMessageMailbox(txn, m, body, bdfMeta);
+		} else return incomingMessageContact(txn, m, body, bdfMeta);
+	}
+
+	private boolean incomingMessageMailbox(Transaction txn, Message m,
+			BdfList body, BdfDictionary bdfMeta)
+			throws DbException, FormatException {
+		// Parse the metadata
+		MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
+		// Look up the session, if there is one
+		SessionId sessionId = meta.getSessionId();
+		MailboxSession newMailboxSession = null;
+		if (sessionId == null) {
+			if (meta.getMessageType() != MAILBOX_REQUEST)
+				throw new AssertionError();
+			newMailboxSession = createNewMailboxSession(txn, m, body);
+			sessionId = newMailboxSession.getSessionId();
+		}
+		StoredSession ss = getSession(txn, sessionId);
+		// Handle the message
+		Session session;
+		MessageId storageId;
+		if (ss == null) {
+			if (meta.getMessageType() != MAILBOX_REQUEST)
+				throw new FormatException();
+			if (newMailboxSession == null) throw new AssertionError();
+			storageId = createStorageId(txn);
+			session = handleMessage(txn, m, body, meta.getMessageType(),
+					newMailboxSession, mailboxProtocolEngine);
+		} else {
+			storageId = ss.storageId;
+			long messageCounter =
+					sessionParser.getMessageCounter(ss.bdfSession);
+			if (meta.getCounter() < messageCounter) {
+				// TODO: ignore or abort?
+				throw new FormatException();
+			}
+			Role role = sessionParser.getRole(ss.bdfSession);
+			if (role == OWNER) {
+				session = handleMessage(txn, m, body, meta.getMessageType(),
+						sessionParser.parseOwnerSession(ss.bdfSession),
+						ownerProtocolEngine);
+			} else if (role == MAILBOX) {
+				session = handleMessage(txn, m, body, meta.getMessageType(),
+						sessionParser.parseMailboxSession(m.getGroupId(),
+								ss.bdfSession), mailboxProtocolEngine);
+			} else throw new AssertionError();
+		}
+		// Store the updated session
+		storeSession(txn, storageId, session);
 		return false;
 	}
 
+	private boolean incomingMessageContact(Transaction txn, Message m,
+			BdfList body, BdfDictionary bdfMeta)
+			throws DbException, FormatException {
+		// Parse the metadata
+		MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
+		// Look up the session, if there is one
+		SessionId sessionId = meta.getSessionId();
+		MailboxSession newMailboxSession = null;
+		if (sessionId == null) {
+			if (meta.getMessageType() != MAILBOX_REQUEST)
+				throw new AssertionError();
+			newMailboxSession = createNewMailboxSession(txn, m, body);
+			sessionId = newMailboxSession.getSessionId();
+		}
+		StoredSession ss = getSession(txn, sessionId);
+		// Handle the message
+		Session session;
+		MessageId storageId;
+		if (ss == null) {
+			if (meta.getMessageType() != MAILBOX_REQUEST)
+				throw new FormatException();
+			if (newMailboxSession == null) throw new AssertionError();
+			storageId = createStorageId(txn);
+			session = handleMessage(txn, m, body, meta.getMessageType(),
+					newMailboxSession, mailboxProtocolEngine);
+		} else {
+			storageId = ss.storageId;
+			long messageCounter =
+					sessionParser.getMessageCounter(ss.bdfSession);
+			if (meta.getCounter() < messageCounter) {
+				// TODO: ignore or abort?
+				throw new FormatException();
+			}
+			Role role = sessionParser.getRole(ss.bdfSession);
+			if (role == OWNER) {
+				session = handleMessage(txn, m, body, meta.getMessageType(),
+						sessionParser.parseOwnerSession(ss.bdfSession),
+						ownerProtocolEngine);
+			} else if (role == MAILBOX) {
+				session = handleMessage(txn, m, body, meta.getMessageType(),
+						sessionParser.parseMailboxSession(m.getGroupId(),
+								ss.bdfSession), mailboxProtocolEngine);
+			} else throw new AssertionError();
+		}
+		// Store the updated session
+		storeSession(txn, storageId, session);
+		return false;
+	}
+
+	private MailboxSession createNewMailboxSession(Transaction txn, Message m,
+			BdfList body) throws DbException, FormatException {
+		ContactId ownerId = getContactId(txn, m.getGroupId());
+		Author owner = db.getContact(txn, ownerId).getAuthor();
+		Author local = identityManager.getLocalAuthor(txn);
+		Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
+		if (local.equals(remote)) throw new FormatException();
+		SessionId sessionId = crypto.getSessionId(owner, local, remote);
+		boolean alice = crypto.isAlice(local.getId(), remote.getId());
+		return MailboxSession
+				.getInitial(m.getGroupId(), sessionId, owner, alice, remote);
+	}
+
+	/*
+	private IntroduceeSession createNewIntroduceeSession(Transaction txn,
+			Message m, BdfList body) throws DbException, FormatException {
+		ContactId ownerId = getContactId(txn, m.getGroupId());
+		Author owner = db.getContact(txn, ownerId).getAuthor();
+		Author local = identityManager.getLocalAuthor(txn);
+		Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
+		if (local.equals(remote)) throw new FormatException();
+		SessionId sessionId = crypto.getSessionId(owner, local, remote);
+		boolean alice = crypto.isAlice(local.getId(), remote.getId());
+		return IntroduceeSession
+				.getInitial(m.getGroupId(), sessionId, owner, alice,
+						remote);
+	}
+*/
+
+	private <S extends Session> S handleMessage(Transaction txn, Message m,
+			BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
+			throws DbException, FormatException {
+		switch (type) {
+
+			case MAILBOX_REQUEST: {
+				RequestMessage request =
+						messageParser.parseRequestMessage(m, body);
+				return engine.onRequestMessage(txn, session, request);
+			}
+			case MAILBOX_ACCEPT: {
+				MailboxAcceptMessage acceptMessage =
+						messageParser.parseMailboxAcceptMessage(m, body);
+				return engine
+						.onMailboxAcceptMessage(txn, session, acceptMessage);
+			}
+			case ABORT: {
+				AbortMessage abort = messageParser.parseAbortMessage(m, body);
+				return engine.onAbortMessage(txn, session, abort);
+			}
+			default:
+				throw new AssertionError();
+		}
+	}
+
+	@Override
+	public Collection<ContactType> getApplicableContactTypes() {
+		return Arrays.asList(values());
+	}
+
 	@Override
 	public void addingContact(Transaction txn, Contact c) throws DbException {
 		switch (c.getType()) {
 			case PRIVATE_MAILBOX:
-				privateMailboxAdded(txn, (PrivateMailbox) c);
-				break;
+			case MAILBOX_OWNER:
 			case CONTACT:
 				contactAdded(txn, c);
 				break;
@@ -108,15 +285,28 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 	}
 
 	@Override
-	public void contactAdded(Transaction txn, Contact contact)
-			throws DbException {
+	public void contactAdded(Transaction txn, Contact c) throws DbException {
+		// Create a group to share with the contact
+		Group g = getContactGroup(c);
+		db.addGroup(txn, g);
+		// Apply the client's visibility to the contact group
+		Group.Visibility client = clientVersioningManager
+				.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
+		db.setGroupVisibility(txn, c.getId(), g.getId(), client);
+		// Attach the contact ID to the group
+		BdfDictionary meta = new BdfDictionary();
+		meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
+		try {
+			clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
 		LOG.info("Contact added");
 	}
 
 	@Override
 	public void privateMailboxAdded(Transaction txn,
-			PrivateMailbox privateMailbox)
-			throws DbException {
+			PrivateMailbox privateMailbox) throws DbException {
 		LOG.info("Private mailbox added");
 	}
 
@@ -179,8 +369,7 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 			Session session) throws DbException {
 		BdfDictionary d;
 		if (session.getRole() == OWNER) {
-			d = sessionEncoder
-					.encodeIntroducerSession((OwnerSession) session);
+			d = sessionEncoder.encodeIntroducerSession((OwnerSession) session);
 		} else if (session.getRole() == MAILBOX) {
 			d = sessionEncoder
 					.encodeIntroduceeSession((IntroduceeSession) session);
@@ -194,6 +383,13 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
+	private ContactId getContactId(Transaction txn, GroupId contactGroupId)
+			throws DbException, FormatException {
+		BdfDictionary meta =
+				clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
+		return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
+	}
+
 	@Override
 	public Group getContactGroup(Contact c) {
 		return contactGroupFactory
@@ -212,15 +408,13 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 		LOG.info("contact removed");
 	}
 
-	@Override
-	public void createLocalState(Transaction txn) throws DbException {
-
-	}
-
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Group.Visibility v) throws DbException {
-
+		if (!getApplicableContactTypes().contains(c.getType())) return;
+		// Apply the client's visibility to the contact group
+		Group g = getContactGroup(c);
+		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
 	}
 
 	private static class StoredSession {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java
index 14420f789..5bf216054 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java
@@ -30,17 +30,19 @@ public class MailboxIntroductionModule {
 		MailboxIntroductionManager mailboxIntroductionManager;
 	}
 
+
 	@Provides
 	@Singleton
-	MailboxIntroductionValidator provideValidator(
+	MailboxIntroductionValidator provideMailboxValidator(
 			ValidationManager validationManager,
-			MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
+			MailboxMessageEncoder messageEncoder,
+			MetadataEncoder metadataEncoder,
 			ClientHelper clientHelper, Clock clock) {
 		MailboxIntroductionValidator mailboxIntroductionValidator =
 				new MailboxIntroductionValidator(messageEncoder, clientHelper,
 						metadataEncoder, clock);
 		validationManager
-				.registerMessageValidator(IntroductionManager.CLIENT_ID,
+				.registerMessageValidator(CLIENT_ID,
 						IntroductionManager.MAJOR_VERSION,
 						mailboxIntroductionValidator);
 		return mailboxIntroductionValidator;
@@ -63,22 +65,26 @@ public class MailboxIntroductionModule {
 	}
 
 	@Provides
-	MessageParser provideMessageParser(MessageParserImpl messageParser) {
+	MailboxMessageParser provideMailboxMessageParser(
+			MailboxMessageParserImpl messageParser) {
 		return messageParser;
 	}
 
 	@Provides
-	MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
+	MailboxMessageEncoder provideMailboxMessageEncoder(
+			MailboxMessageEncoderImpl messageEncoder) {
 		return messageEncoder;
 	}
 
 	@Provides
-	SessionParser provideSessionParser(SessionParserImpl sessionParser) {
+	MailboxSessionParser provideMailboxSessionParser(
+			MailboxSessionParserImpl sessionParser) {
 		return sessionParser;
 	}
 
 	@Provides
-	SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
+	MailboxSessionEncoder provideMailboxSessionEncoder(
+			MailboxSessionEncoderImpl sessionEncoder) {
 		return sessionEncoder;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java
index 46cf6776f..144fd6d88 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java
@@ -24,7 +24,6 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
-import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH;
 
@@ -33,10 +32,9 @@ import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH;
 @NotNullByDefault
 class MailboxIntroductionValidator extends BdfMessageValidator {
 
-	private final MessageEncoder messageEncoder;
+	private final MailboxMessageEncoder messageEncoder;
 
-	MailboxIntroductionValidator(
-			MessageEncoder messageEncoder,
+	MailboxIntroductionValidator(MailboxMessageEncoder messageEncoder,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		super(clientHelper, metadataEncoder, clock);
@@ -66,18 +64,13 @@ class MailboxIntroductionValidator extends BdfMessageValidator {
 	private BdfMessageContext validateRequestMessage(Message m, BdfList body)
 			throws FormatException {
 		checkSize(body, 4);
-
 		byte[] previousMessageId = body.getOptionalRaw(1);
 		checkLength(previousMessageId, UniqueId.LENGTH);
-
 		BdfList authorList = body.getList(2);
 		clientHelper.parseAndValidateAuthor(authorList);
-
-		String msg = body.getOptionalString(3);
-		checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
-
-		BdfDictionary meta =
-				messageEncoder.encodeRequestMetadata(m.getTimestamp());
+		long messageCounter = body.getLong(3);
+		BdfDictionary meta = messageEncoder
+				.encodeRequestMetadata(m.getTimestamp(), messageCounter);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
@@ -103,15 +96,12 @@ class MailboxIntroductionValidator extends BdfMessageValidator {
 		long timestamp = body.getLong(4);
 		if (timestamp < 0) throw new FormatException();
 
-		BdfDictionary transportProperties = body.getDictionary(5);
-		if (transportProperties.size() < 1) throw new FormatException();
-		clientHelper
-				.parseAndValidateTransportPropertiesMap(transportProperties);
+		long messageCounter = body.getLong(5);
 
 		SessionId sessionId = new SessionId(sessionIdBytes);
 		BdfDictionary meta = messageEncoder
 				.encodeMetadata(MAILBOX_ACCEPT, sessionId, m.getTimestamp(),
-						false);
+						false, messageCounter);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
@@ -140,15 +130,14 @@ class MailboxIntroductionValidator extends BdfMessageValidator {
 		SessionId sessionId = new SessionId(sessionIdBytes);
 		BdfDictionary meta = messageEncoder
 				.encodeMetadata(MAILBOX_AUTH, sessionId, m.getTimestamp(),
-						false);
+						false, 0);
 		MessageId dependency = new MessageId(previousMessageId);
 		return new BdfMessageContext(meta,
 				Collections.singletonList(dependency));
 	}
 
-	private BdfMessageContext validateOtherMessage(
-			MessageType type,
-			Message m, BdfList body) throws FormatException {
+	private BdfMessageContext validateOtherMessage(MessageType type, Message m,
+			BdfList body) throws FormatException {
 		checkSize(body, 3);
 
 		byte[] sessionIdBytes = body.getRaw(1);
@@ -159,7 +148,7 @@ class MailboxIntroductionValidator extends BdfMessageValidator {
 
 		SessionId sessionId = new SessionId(sessionIdBytes);
 		BdfDictionary meta = messageEncoder
-				.encodeMetadata(type, sessionId, m.getTimestamp(), false);
+				.encodeMetadata(type, sessionId, m.getTimestamp(), false, 0);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
similarity index 71%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
index cc44d68c2..dc9b032e1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
@@ -1,39 +1,36 @@
 package org.briarproject.briar.mailbox;
 
 import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.SessionId;
 
-import java.util.Map;
-
 import javax.annotation.Nullable;
 
 @NotNullByDefault
-interface MessageEncoder {
+interface MailboxMessageEncoder {
 
-	BdfDictionary encodeRequestMetadata(long timestamp);
+	BdfDictionary encodeRequestMetadata(long timestamp, long messageCounter);
 
 	BdfDictionary encodeMetadata(MessageType type,
-			@Nullable SessionId sessionId, long timestamp, boolean local);
+			@Nullable SessionId sessionId, long timestamp, boolean local,
+			long messsageCounter);
 
 	void addSessionId(BdfDictionary meta, SessionId sessionId);
 
-	void setVisibleInUi(BdfDictionary meta, boolean visible);
-
 	void setAvailableToAnswer(BdfDictionary meta, boolean available);
 
 	Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId);
+			@Nullable MessageId previousMessageId, Author introduceeAuthor,
+			long messageCounter);
 
 	Message encodeMailboxAcceptMessage(GroupId contactGroupId, long timestamp,
 			@Nullable MessageId previousMessageId, SessionId sessionId,
 			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties);
+			long messageCounter);
 
 	Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
 			@Nullable MessageId previousMessageId, SessionId sessionId);
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
similarity index 72%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoderImpl.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
index dfb5dfd3c..fc2ddcff7 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
@@ -4,64 +4,62 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.SessionId;
 
-import java.util.Map;
-
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_LOCAL;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_SESSION_ID;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_TIMESTAMP;
-import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
 import static org.briarproject.briar.mailbox.MessageType.ABORT;
 import static org.briarproject.briar.mailbox.MessageType.DECLINE;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 
 @NotNullByDefault
-class MessageEncoderImpl implements
-		MessageEncoder {
+class MailboxMessageEncoderImpl implements MailboxMessageEncoder {
 
 	private final ClientHelper clientHelper;
 	private final MessageFactory messageFactory;
 
 	@Inject
-	MessageEncoderImpl(ClientHelper clientHelper,
+	MailboxMessageEncoderImpl(ClientHelper clientHelper,
 			MessageFactory messageFactory) {
 		this.clientHelper = clientHelper;
 		this.messageFactory = messageFactory;
 	}
 
 	@Override
-	public BdfDictionary encodeRequestMetadata(long timestamp) {
+	public BdfDictionary encodeRequestMetadata(long timestamp,
+			long messageCounter) {
 		BdfDictionary meta =
-				encodeMetadata(MAILBOX_REQUEST, null, timestamp, false);
+				encodeMetadata(MAILBOX_REQUEST, null, timestamp, false,
+						messageCounter);
 		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
 		return meta;
 	}
 
 	@Override
 	public BdfDictionary encodeMetadata(MessageType type,
-			@Nullable SessionId sessionId, long timestamp, boolean local) {
+			@Nullable SessionId sessionId, long timestamp, boolean local,
+			long messageCounter) {
 		BdfDictionary meta = new BdfDictionary();
 		meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue());
-		if (sessionId != null)
-			meta.put(MSG_KEY_SESSION_ID, sessionId);
-		else if (type != MAILBOX_REQUEST)
-			throw new IllegalArgumentException();
+		if (sessionId != null) meta.put(MSG_KEY_SESSION_ID, sessionId);
+		else if (type != MAILBOX_REQUEST) throw new IllegalArgumentException();
 		meta.put(MSG_KEY_TIMESTAMP, timestamp);
 		meta.put(MSG_KEY_LOCAL, local);
+		meta.put(MSG_KEY_COUNTER, messageCounter);
 		return meta;
 	}
 
@@ -70,11 +68,6 @@ class MessageEncoderImpl implements
 		meta.put(MSG_KEY_SESSION_ID, sessionId);
 	}
 
-	@Override
-	public void setVisibleInUi(BdfDictionary meta, boolean visible) {
-		meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
-	}
-
 	@Override
 	public void setAvailableToAnswer(BdfDictionary meta, boolean available) {
 		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
@@ -82,28 +75,21 @@ class MessageEncoderImpl implements
 
 	@Override
 	public Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId) {
-		BdfList body = BdfList.of(
-				MAILBOX_REQUEST.getValue(),
-				previousMessageId
-		);
+			@Nullable MessageId previousMessageId, Author introduceeAuthor,
+			long messageCounter) {
+		BdfList body = BdfList.of(MAILBOX_REQUEST.getValue(), previousMessageId,
+				clientHelper.toList(introduceeAuthor), messageCounter);
 		return createMessage(contactGroupId, timestamp, body);
 	}
 
 	@Override
 	public Message encodeMailboxAcceptMessage(GroupId contactGroupId,
-			long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties) {
-		BdfList body = BdfList.of(
-				MAILBOX_ACCEPT.getValue(),
-				sessionId,
-				previousMessageId,
-				ephemeralPublicKey,
-				acceptTimestamp,
-				clientHelper.toDictionary(transportProperties)
-		);
+			long timestamp, @Nullable MessageId previousMessageId,
+			SessionId sessionId, byte[] ephemeralPublicKey,
+			long acceptTimestamp, long messageCounter) {
+		BdfList body = BdfList.of(MAILBOX_ACCEPT.getValue(), sessionId,
+				previousMessageId, ephemeralPublicKey, acceptTimestamp,
+				messageCounter);
 		return createMessage(contactGroupId, timestamp, body);
 	}
 
@@ -124,11 +110,8 @@ class MessageEncoderImpl implements
 	private Message encodeMessage(MessageType type, GroupId contactGroupId,
 			SessionId sessionId, long timestamp,
 			@Nullable MessageId previousMessageId) {
-		BdfList body = BdfList.of(
-				type.getValue(),
-				sessionId,
-				previousMessageId
-		);
+		BdfList body =
+				BdfList.of(type.getValue(), sessionId, previousMessageId);
 		return createMessage(contactGroupId, timestamp, body);
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
similarity index 85%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParser.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
index 640178c99..7370e7bd2 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParser.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
@@ -8,12 +8,12 @@ import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.briar.api.client.SessionId;
 
 @NotNullByDefault
-interface MessageParser {
-
-	BdfDictionary getMessagesVisibleInUiQuery();
+interface MailboxMessageParser {
 
 	BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId);
 
+	MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
+
 	//MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
 
 	RequestMessage parseRequestMessage(Message m, BdfList body)
@@ -28,8 +28,7 @@ interface MessageParser {
 	MailboxAuthMessage parseMailboxAuthMessage(Message m, BdfList body)
 			throws FormatException;
 
-	AbortMessage parseAbortMessage(
-			Message m, BdfList body)
+	AbortMessage parseAbortMessage(Message m, BdfList body)
 			throws FormatException;
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
similarity index 74%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParserImpl.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
index ec0e13baa..93e72e916 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
@@ -5,50 +5,40 @@ import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.SessionId;
 
-import java.util.Map;
-
 import javax.inject.Inject;
 
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_LOCAL;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_SESSION_ID;
-import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_TIMESTAMP;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 
 @NotNullByDefault
-class MessageParserImpl implements
-		MessageParser {
+class MailboxMessageParserImpl implements MailboxMessageParser {
 
 	private final ClientHelper clientHelper;
 
 	@Inject
-	MessageParserImpl(ClientHelper clientHelper) {
+	MailboxMessageParserImpl(ClientHelper clientHelper) {
 		this.clientHelper = clientHelper;
 	}
 
-	@Override
-	public BdfDictionary getMessagesVisibleInUiQuery() {
-		return BdfDictionary.of(new BdfEntry(MSG_KEY_VISIBLE_IN_UI, true));
-	}
-
 	@Override
 	public BdfDictionary getRequestsAvailableToAnswerQuery(
 			SessionId sessionId) {
-		return BdfDictionary.of(
-				new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
+		return BdfDictionary.of(new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
 				new BdfEntry(MSG_KEY_MESSAGE_TYPE, MAILBOX_REQUEST.getValue()),
-				new BdfEntry(MSG_KEY_SESSION_ID, sessionId)
-		);
+				new BdfEntry(MSG_KEY_SESSION_ID, sessionId));
 	}
 
-	/*
 	@Override
 	public MessageMetadata parseMetadata(BdfDictionary d)
 			throws FormatException {
@@ -59,13 +49,11 @@ class MessageParserImpl implements
 				sessionIdBytes == null ? null : new SessionId(sessionIdBytes);
 		long timestamp = d.getLong(MSG_KEY_TIMESTAMP);
 		boolean local = d.getBoolean(MSG_KEY_LOCAL);
-		boolean read = d.getBoolean(MSG_KEY_READ);
-		boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI);
+		long counter = d.getLong(MSG_KEY_COUNTER);
 		boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
-		return new MessageMetadata(type, sessionId, timestamp, local, read,
-				visible, available);
+		return new MessageMetadata(type, sessionId, timestamp, local, counter,
+				available);
 	}
-	*/
 
 	@Override
 	public RequestMessage parseRequestMessage(Message m, BdfList body)
@@ -73,26 +61,23 @@ class MessageParserImpl implements
 		byte[] previousMsgBytes = body.getOptionalRaw(1);
 		MessageId previousMessageId = (previousMsgBytes == null ? null :
 				new MessageId(previousMsgBytes));
-		return new RequestMessage(m.getId(), m.getGroupId(),
-				m.getTimestamp(), previousMessageId, body.getLong(2));
+		Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
+		return new RequestMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
+				previousMessageId, author);
 	}
 
 	@Override
 	public MailboxAcceptMessage parseMailboxAcceptMessage(Message m,
-			BdfList body)
-			throws FormatException {
+			BdfList body) throws FormatException {
 		SessionId sessionId = new SessionId(body.getRaw(1));
 		byte[] previousMsgBytes = body.getOptionalRaw(2);
 		MessageId previousMessageId = (previousMsgBytes == null ? null :
 				new MessageId(previousMsgBytes));
 		byte[] ephemeralPublicKey = body.getRaw(3);
 		long acceptTimestamp = body.getLong(4);
-		Map<TransportId, TransportProperties> transportProperties = clientHelper
-				.parseAndValidateTransportPropertiesMap(body.getDictionary(5));
 		return new MailboxAcceptMessage(m.getId(), m.getGroupId(),
-				m.getTimestamp(),
-				previousMessageId, sessionId, ephemeralPublicKey,
-				acceptTimestamp, transportProperties, body.getLong(5));
+				m.getTimestamp(), previousMessageId, sessionId,
+				ephemeralPublicKey, acceptTimestamp);
 	}
 
 	@Override
@@ -103,7 +88,7 @@ class MessageParserImpl implements
 		MessageId previousMessageId = (previousMsgBytes == null ? null :
 				new MessageId(previousMsgBytes));
 		return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
-				previousMessageId, sessionId, body.getLong(3));
+				previousMessageId, sessionId);
 	}
 
 	@Override
@@ -122,15 +107,14 @@ class MessageParserImpl implements
 	}
 
 	@Override
-	public AbortMessage parseAbortMessage(
-			Message m, BdfList body)
+	public AbortMessage parseAbortMessage(Message m, BdfList body)
 			throws FormatException {
 		SessionId sessionId = new SessionId(body.getRaw(1));
 		byte[] previousMsgBytes = body.getOptionalRaw(2);
 		MessageId previousMessageId = (previousMsgBytes == null ? null :
 				new MessageId(previousMsgBytes));
 		return new AbortMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
-				previousMessageId, sessionId, body.getLong(3));
+				previousMessageId, sessionId);
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
new file mode 100644
index 000000000..901c6efca
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
@@ -0,0 +1,182 @@
+package org.briarproject.briar.mailbox;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.client.ContactGroupFactory;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.db.DatabaseComponent;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.api.client.MessageTracker;
+import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE;
+
+class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> {
+
+	@Inject
+	MailboxProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
+			ContactManager contactManager,
+			ContactGroupFactory contactGroupFactory,
+			MessageTracker messageTracker, IdentityManager identityManager,
+			MailboxMessageParser messageParser,
+			MailboxMessageEncoder messageEncoder, Clock clock,
+			MailboxIntroductionCrypto crypto) {
+		super(db, clientHelper, contactManager, contactGroupFactory,
+				messageTracker, identityManager, messageParser, messageEncoder,
+				clock, crypto);
+	}
+
+	@Override
+	public MailboxSession onRequestAction(Transaction txn,
+			MailboxSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onAcceptAction(Transaction txn,
+			MailboxSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onDeclineAction(Transaction txn,
+			MailboxSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onRequestMessage(Transaction txn,
+			MailboxSession session, RequestMessage m)
+			throws DbException, FormatException {
+		switch (session.getState()) {
+			case START:
+				return onRemoteRequest(txn, session, m);
+			case AWAIT_LOCAL_RESPONSE:
+			case LOCAL_DECLINED:
+			case LOCAL_ACCEPTED:
+			case AWAIT_REMOTE_RESPONSE:
+			case MAILBOX_ADDED:
+				return abort(txn, session);
+			default:
+				throw new AssertionError();
+		}
+	}
+
+	private MailboxSession onRemoteRequest(Transaction txn, MailboxSession s,
+			RequestMessage m) throws DbException {
+		// The dependency, if any, must be the last remote message
+		if (isInvalidDependency(s, m.getPreviousMessageId()))
+			return abort(txn, s);
+
+		// Add SessionId to message metadata
+		addSessionId(txn, m.getMessageId(), s.getSessionId());
+
+		// Broadcast IntroductionRequestReceivedEvent
+		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
+		txn.attach(new MailboxIntroductionRequestReceivedEvent(
+				localAuthor.getId()));
+
+		// Create ephemeral key pair and get local transport properties
+		KeyPair keyPair = crypto.generateKeyPair();
+		byte[] publicKey = keyPair.getPublic().getEncoded();
+		byte[] privateKey = keyPair.getPrivate().getEncoded();
+		long localTimestamp = clock.currentTimeMillis();
+		// Send ephemeral public key and timestamp back
+		Message reply =
+				sendMailboxAcceptMessage(txn, s, localTimestamp, publicKey,
+						localTimestamp, s.getMessageCounter());
+
+		//TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE
+		// Move to the AWAIT_REMOTE_RESPONSE state
+		return MailboxSession
+				.addLocalAccept(s, AWAIT_REMOTE_RESPONSE, reply, publicKey,
+						privateKey, localTimestamp);
+	}
+
+	private MailboxSession abort(Transaction txn, MailboxSession s)
+			throws DbException {
+		/*
+		// Mark the request message unavailable to answer
+		markRequestsUnavailableToAnswer(txn, s);
+
+		// Send an ABORT message
+		Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s));
+
+		// Broadcast abort event for testing
+		txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
+
+		// Reset the session back to initial state
+		return IntroduceeSession.clear(s, START, sent.getId(),
+				sent.getTimestamp(), s.getLastRemoteMessageId());
+				*/
+		return null;
+	}
+
+	@Override
+	public MailboxSession onMailboxAcceptMessage(Transaction txn,
+			MailboxSession session, MailboxAcceptMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onContactAcceptMessage(Transaction txn,
+			MailboxSession session, MailboxAcceptMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onDeclineMessage(Transaction txn,
+			MailboxSession session, DeclineMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onAuthMessage(Transaction txn, MailboxSession session,
+			MailboxAuthMessage m) throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public MailboxSession onAbortMessage(Transaction txn,
+			MailboxSession session, AbortMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+
+	private boolean isInvalidDependency(IntroduceeSession s,
+			@Nullable MessageId dependency) {
+		return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
+	}
+
+	private long getLocalTimestamp(IntroduceeSession s) {
+		return getLocalTimestamp(s.getLocalTimestamp(),
+				s.getRequestTimestamp());
+	}
+
+	private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)
+			throws DbException {
+		BdfDictionary meta = new BdfDictionary();
+		messageEncoder.addSessionId(meta, sessionId);
+		try {
+			clientHelper.mergeMessageMetadata(txn, m, meta);
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
index edf1f0957..54f46c749 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
@@ -36,8 +36,7 @@ class MailboxSession extends IntroduceeSession {
 	static MailboxSession getInitial(GroupId contactGroupId,
 			SessionId sessionId, Author introducer, boolean localIsAlice,
 			Author remoteAuthor) {
-		Local local =
-				new Local(localIsAlice, null, -1, null, null, -1, null);
+		Local local = new Local(localIsAlice, null, -1, null, null, -1, null);
 		Remote remote =
 				new Remote(!localIsAlice, remoteAuthor, null, null, null, -1,
 						null);
@@ -45,28 +44,19 @@ class MailboxSession extends IntroduceeSession {
 				introducer, local, remote, null, null, 0);
 	}
 
-	static MailboxSession addRemoteRequest(
-			MailboxSession s,
-			IntroduceeState state,
-			ContactRequestMessage m, byte[] ephemeralPublicKey,
-			byte[] ephemeralPrivateKey,
-			long acceptTimestamp, boolean alice) {
-		Local local = new Local(alice, m.getMessageId(),
-				m.getTimestamp(), ephemeralPublicKey, ephemeralPrivateKey,
-				acceptTimestamp, null);
-		Remote remote =
-				new Remote(!alice, s.remote.author, m.getMessageId(),
-						m.getEphemeralPublicKey(), null, m.getAcceptTimestamp(),
-						null);
+	static MailboxSession addLocalAccept(MailboxSession s,
+			IntroduceeState state, Message m, byte[] ephemeralPublicKey,
+			byte[] ephemeralPrivateKey, long acceptTimestamp) {
+		Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(),
+				ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, null);
 		return new MailboxSession(s.getSessionId(), state, m.getTimestamp(),
-				s.contactGroupId, s.introducer, local, remote, s.masterKey,
-				s.transportKeys, s.getSessionCounter());
+				s.contactGroupId, s.introducer, local, s.remote, s.masterKey,
+				s.transportKeys, s.getMessageCounter() + 1);
 	}
 
-	static MailboxSession addLocalAuth(
-			MailboxSession s,
-			IntroduceeState state, Message m, SecretKey masterKey,
-			SecretKey aliceMacKey, SecretKey bobMacKey) {
+	static MailboxSession addLocalAuth(MailboxSession s, IntroduceeState state,
+			Message m, SecretKey masterKey, SecretKey aliceMacKey,
+			SecretKey bobMacKey) {
 		// add mac key and sent message
 		Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(),
 				s.local.ephemeralPublicKey, s.local.ephemeralPrivateKey,
@@ -81,21 +71,19 @@ class MailboxSession extends IntroduceeSession {
 		return new MailboxSession(s.getSessionId(), state,
 				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
 				remote, masterKey.getBytes(), s.transportKeys,
-				s.getSessionCounter());
+				s.getMessageCounter());
 	}
 
-	static MailboxSession awaitAuth(
-			MailboxSession s, MailboxAuthMessage m,
+	static MailboxSession awaitAuth(MailboxSession s, MailboxAuthMessage m,
 			Message sent, @Nullable Map<TransportId, KeySetId> transportKeys) {
 		Local local = new Local(s.local, sent.getId(), sent.getTimestamp());
 		Remote remote = new Remote(s.remote, m.getMessageId());
 		return new MailboxSession(s.getSessionId(), AWAIT_REMOTE_RESPONSE,
 				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
-				remote, null, transportKeys, s.getSessionCounter());
+				remote, null, transportKeys, s.getMessageCounter());
 	}
 
-	static MailboxSession clear(
-			MailboxSession s, IntroduceeState state,
+	static MailboxSession clear(MailboxSession s, IntroduceeState state,
 			@Nullable MessageId lastLocalMessageId, long localTimestamp,
 			@Nullable MessageId lastRemoteMessageId) {
 		Local local =
@@ -106,7 +94,7 @@ class MailboxSession extends IntroduceeSession {
 						null, null, -1, null);
 		return new MailboxSession(s.getSessionId(), state,
 				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
-				remote, null, null, s.getSessionCounter());
+				remote, null, null, s.getMessageCounter());
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
similarity index 93%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoder.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
index c781f014a..6ac94a2a3 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
 @NotNullByDefault
-interface SessionEncoder {
+interface MailboxSessionEncoder {
 
 	BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer);
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
similarity index 98%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoderImpl.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
index 413048223..d8ef32f1c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
@@ -45,13 +45,13 @@ import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_T
 
 @Immutable
 @NotNullByDefault
-class SessionEncoderImpl implements
-		SessionEncoder {
+class MailboxSessionEncoderImpl implements
+		MailboxSessionEncoder {
 
 	private final ClientHelper clientHelper;
 
 	@Inject
-	SessionEncoderImpl(ClientHelper clientHelper) {
+	MailboxSessionEncoderImpl(ClientHelper clientHelper) {
 		this.clientHelper = clientHelper;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java
similarity index 87%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParser.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java
index a66a24f70..0d42aefd7 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParser.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java
@@ -8,12 +8,14 @@ import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.mailbox.Role;
 
 @NotNullByDefault
-interface SessionParser {
+interface MailboxSessionParser {
 
 	BdfDictionary getSessionQuery(SessionId s);
 
 	Role getRole(BdfDictionary d) throws FormatException;
 
+	long getMessageCounter(BdfDictionary d) throws FormatException;
+
 	OwnerSession parseOwnerSession(BdfDictionary d)
 			throws FormatException;
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
similarity index 89%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParserImpl.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
index ebc687e95..d3f995611 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/SessionParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
@@ -24,6 +24,9 @@ import javax.inject.Inject;
 import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE;
 import static org.briarproject.briar.api.mailbox.Role.OWNER;
 import static org.briarproject.briar.api.mailbox.Role.fromValue;
+import static org.briarproject.briar.mailbox.IntroduceeSession.Local;
+import static org.briarproject.briar.mailbox.IntroduceeSession.Remote;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ALICE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR;
@@ -48,17 +51,15 @@ import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_S
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_STATE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
-import static org.briarproject.briar.mailbox.MailboxSession.Local;
-import static org.briarproject.briar.mailbox.MailboxSession.Remote;
 
 @Immutable
 @NotNullByDefault
-class SessionParserImpl implements SessionParser {
+class MailboxSessionParserImpl implements MailboxSessionParser {
 
 	private final ClientHelper clientHelper;
 
 	@Inject
-	SessionParserImpl(ClientHelper clientHelper) {
+	MailboxSessionParserImpl(ClientHelper clientHelper) {
 		this.clientHelper = clientHelper;
 	}
 
@@ -72,6 +73,11 @@ class SessionParserImpl implements SessionParser {
 		return fromValue(d.getLong(SESSION_KEY_ROLE).intValue());
 	}
 
+	@Override
+	public long getMessageCounter(BdfDictionary d) throws FormatException {
+		return d.getLong(MSG_KEY_COUNTER);
+	}
+
 	@Override
 	public OwnerSession parseOwnerSession(BdfDictionary d)
 			throws FormatException {
@@ -84,12 +90,11 @@ class SessionParserImpl implements SessionParser {
 				d.getDictionary(SESSION_KEY_INTRODUCEE_A));
 		Introducee introduceeB = parseIntroducee(sessionId,
 				d.getDictionary(SESSION_KEY_INTRODUCEE_B));
-		return new OwnerSession(sessionId, state, requestTimestamp,
-				introduceeA, introduceeB, sessionCounter);
+		return new OwnerSession(sessionId, state, requestTimestamp, introduceeA,
+				introduceeB, sessionCounter);
 	}
 
-	private Introducee parseIntroducee(SessionId sessionId,
-			BdfDictionary d)
+	private Introducee parseIntroducee(SessionId sessionId, BdfDictionary d)
 			throws FormatException {
 		MessageId lastLocalMessageId =
 				getMessageId(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
@@ -98,14 +103,12 @@ class SessionParserImpl implements SessionParser {
 		long localTimestamp = d.getLong(SESSION_KEY_LOCAL_TIMESTAMP);
 		GroupId groupId = getGroupId(d, SESSION_KEY_GROUP_ID);
 		Author author = getAuthor(d, SESSION_KEY_AUTHOR);
-		return new Introducee(sessionId, groupId, author,
-				localTimestamp,
+		return new Introducee(sessionId, groupId, author, localTimestamp,
 				lastLocalMessageId, lastRemoteMessageId);
 	}
 
 	@Override
-	public MailboxSession parseMailboxSession(
-			GroupId introducerGroupId,
+	public MailboxSession parseMailboxSession(GroupId introducerGroupId,
 			BdfDictionary d) throws FormatException {
 		if (getRole(d) != INTRODUCEE) throw new IllegalArgumentException();
 		SessionId sessionId = getSessionId(d);
@@ -119,8 +122,8 @@ class SessionParserImpl implements SessionParser {
 		Map<TransportId, KeySetId> transportKeys = parseTransportKeys(
 				d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS));
 		return new MailboxSession(sessionId, state, requestTimestamp,
-				introducerGroupId, introducer, local, remote,
-				masterKey, transportKeys, sessionCounter);
+				introducerGroupId, introducer, local, remote, masterKey,
+				transportKeys, sessionCounter);
 	}
 
 	private Local parseLocal(BdfDictionary d) throws FormatException {
@@ -158,8 +161,7 @@ class SessionParserImpl implements SessionParser {
 						.parseAndValidateTransportPropertiesMap(tpDict);
 		long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP);
 		byte[] macKey = d.getOptionalRaw(SESSION_KEY_MAC_KEY);
-		return new Remote(alice, remoteAuthor,
-				lastRemoteMessageId,
+		return new Remote(alice, remoteAuthor, lastRemoteMessageId,
 				ephemeralPublicKey, transportProperties, acceptTimestamp,
 				macKey);
 	}
@@ -201,8 +203,7 @@ class SessionParserImpl implements SessionParser {
 		Map<TransportId, KeySetId> map = new HashMap<>(d.size());
 		for (String key : d.keySet()) {
 			map.put(new TransportId(key),
-					new KeySetId(d.getLong(key).intValue())
-			);
+					new KeySetId(d.getLong(key).intValue()));
 		}
 		return map;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java
new file mode 100644
index 000000000..9c0c50617
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java
@@ -0,0 +1,55 @@
+package org.briarproject.briar.mailbox;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.briar.api.client.SessionId;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+class MessageMetadata {
+
+	private final MessageType type;
+	@Nullable
+	private final SessionId sessionId;
+	private final long timestamp, counter;
+	private final boolean local;
+	private final boolean available;
+
+	MessageMetadata(MessageType type, @Nullable SessionId sessionId,
+			long timestamp, boolean local, long counter, boolean available) {
+		this.type = type;
+		this.sessionId = sessionId;
+		this.timestamp = timestamp;
+		this.local = local;
+		this.counter = counter;
+		this.available = available;
+	}
+
+	MessageType getMessageType() {
+		return type;
+	}
+
+	@Nullable
+	public SessionId getSessionId() {
+		return sessionId;
+	}
+
+	long getTimestamp() {
+		return timestamp;
+	}
+
+	boolean isLocal() {
+		return local;
+	}
+
+	long getCounter() {
+		return counter;
+	}
+
+	boolean isAvailableToAnswer() {
+		return available;
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
index 3aa608b08..adb82656b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
@@ -7,38 +7,37 @@ import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.ProtocolStateException;
+import org.briarproject.briar.api.mailbox.event.MailboxIntroductionResponseReceivedEvent;
 
 import javax.inject.Inject;
 
+import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_B;
 import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_M;
 
 class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 
 	@Inject
-	OwnerProtocolEngine(
-			DatabaseComponent db,
-			ClientHelper clientHelper,
+	OwnerProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
 			ContactManager contactManager,
 			ContactGroupFactory contactGroupFactory,
-			MessageTracker messageTracker,
-			IdentityManager identityManager,
-			MessageParser messageParser,
-			MessageEncoder messageEncoder,
-			Clock clock) {
+			MessageTracker messageTracker, IdentityManager identityManager,
+			MailboxMessageParser messageParser,
+			MailboxMessageEncoder messageEncoder, Clock clock,
+			MailboxIntroductionCrypto crypto) {
 		super(db, clientHelper, contactManager, contactGroupFactory,
 				messageTracker, identityManager, messageParser, messageEncoder,
-				clock);
+				clock, crypto);
 	}
 
 	@Override
 	public OwnerSession onRequestAction(Transaction txn, OwnerSession s,
-			long timestamp)
-			throws DbException {
+			long timestamp) throws DbException {
 		switch (s.getState()) {
 			case START:
 				return onLocalRequest(txn, s, timestamp);
@@ -69,7 +68,7 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 	@Override
 	public OwnerSession onRequestMessage(Transaction txn, OwnerSession session,
 			RequestMessage m) throws DbException, FormatException {
-//		return abort(txn, session);
+		//		return abort(txn, session);
 		throw new UnsupportedOperationException();
 	}
 
@@ -98,9 +97,21 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 		throw new UnsupportedOperationException();
 	}
 
-	private void onMailboxAccept(Transaction txn, OwnerSession s,
-			MailboxAcceptMessage m) {
-
+	private OwnerSession onMailboxAccept(Transaction txn, OwnerSession s,
+			MailboxAcceptMessage m) throws DbException {
+		// The dependency, if any, must be the last remote message
+		if (isInvalidDependency(s.getMailbox().lastRemoteMessageId,
+				m.getPreviousMessageId())) return abort(txn, s);
+		Message forward = sendMailboxAcceptMessage(txn, s.getIntroducee(),
+				clock.currentTimeMillis(), m.getEphemeralPublicKey(),
+				m.getAcceptTimestamp(), s.getMessageCounter());
+		broadcastMailboxIntroductionResponseReceived(txn, s.getMailbox().author,
+				s.getIntroducee().author);
+		return new OwnerSession(s.getSessionId(), AWAIT_RESPONSE_B,
+				s.getRequestTimestamp(),
+				new Introducee(s.getMailbox(), m.getMessageId()),
+				new Introducee(s.getIntroducee(), forward),
+				s.getMessageCounter() + 1);
 	}
 
 	@Override
@@ -122,29 +133,37 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 	}
 
 	private OwnerSession onLocalRequest(Transaction txn, OwnerSession s,
-			long timestamp)
-			throws DbException {
+			long timestamp) throws DbException {
 		// Send REQUEST messages
 		long maxIntroduceeTimestamp =
 				Math.max(getLocalTimestamp(s, s.getMailbox()),
 						getLocalTimestamp(s, s.getIntroducee()));
 		long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp);
 		Message sentMailbox =
-				sendMailboxRequestMessage(txn, s.getMailbox(), localTimestamp);
-        /*
-        // Track the message
-        messageTracker.trackOutgoingMessage(txn, sentMailbox);
-        */
+				sendMailboxRequestMessage(txn, s.getMailbox(), localTimestamp,
+						s.getIntroducee().author, s.getMessageCounter());
 		// Move to the AWAIT_RESPONSES state
 		Introducee mailbox = new Introducee(s.getMailbox(), sentMailbox);
 		Introducee b = new Introducee(s.getIntroducee().sessionId,
 				s.getIntroducee().groupId, s.getIntroducee().author);
 		return new OwnerSession(s.getSessionId(), AWAIT_RESPONSE_M,
-				localTimestamp, mailbox, b, s.getSessionCounter());
+				localTimestamp, mailbox, b, s.getMessageCounter());
 	}
 
 	private long getLocalTimestamp(OwnerSession s, PeerSession p) {
 		return getLocalTimestamp(p.getLocalTimestamp(),
 				s.getRequestTimestamp());
 	}
+
+	private OwnerSession abort(Transaction txn, Session s) {
+		return null;
+	}
+
+	void broadcastMailboxIntroductionResponseReceived(Transaction txn,
+			Author from, Author to) {
+		MailboxIntroductionResponseReceivedEvent e =
+				new MailboxIntroductionResponseReceivedEvent(to, from);
+		txn.attach(e);
+	}
+
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java
index d63caea3d..ec7c9c448 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java
@@ -9,6 +9,7 @@ import org.briarproject.briar.api.mailbox.Role;
 import javax.annotation.concurrent.Immutable;
 
 import static org.briarproject.briar.api.mailbox.Role.MAILBOX;
+import static org.briarproject.briar.api.mailbox.Role.OWNER;
 
 @Immutable
 @NotNullByDefault
@@ -33,7 +34,7 @@ class OwnerSession extends Session<OwnerState> {
 
 	@Override
 	Role getRole() {
-		return MAILBOX;
+		return OWNER;
 	}
 
 	Introducee getMailbox() {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java
index 5c060c9e3..2e25c9094 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java
@@ -1,5 +1,6 @@
 package org.briarproject.briar.mailbox;
 
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
@@ -11,9 +12,17 @@ import javax.annotation.concurrent.Immutable;
 @NotNullByDefault
 class RequestMessage extends AbstractMailboxIntroductionMessage {
 
+
+	private final Author author;
+
 	protected RequestMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
-			long messageCounter) {
-		super(messageId, groupId, timestamp, previousMessageId, messageCounter);
+			Author author) {
+		super(messageId, groupId, timestamp, previousMessageId);
+		this.author = author;
+	}
+
+	public Author getAuthor() {
+		return author;
 	}
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java
index 5f1305600..5e7a75bac 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java
@@ -13,14 +13,14 @@ abstract class Session<S extends State> {
 	private final SessionId sessionId;
 	private final S state;
 	private final long requestTimestamp;
-	private final long sessionCounter;
+	private long messageCounter;
 
 	Session(SessionId sessionId, S state, long requestTimestamp,
-			long sessionCounter) {
+			long messageCounter) {
 		this.sessionId = sessionId;
 		this.state = state;
 		this.requestTimestamp = requestTimestamp;
-		this.sessionCounter = sessionCounter;
+		this.messageCounter = messageCounter;
 	}
 
 	abstract Role getRole();
@@ -37,8 +37,12 @@ abstract class Session<S extends State> {
 		return requestTimestamp;
 	}
 
-	long getSessionCounter() {
-		return sessionCounter;
+	synchronized long getMessageCounter() {
+		return messageCounter;
+	}
+
+	synchronized void increaseMessageCounter() {
+		messageCounter++;
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java
index af46561a3..f70c239ed 100644
--- a/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/messaging/MessagingManagerImpl.java
@@ -107,6 +107,7 @@ class MessagingManagerImpl extends ConversationClientImpl
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility v) throws DbException {
+		if(!getApplicableContactTypes().contains(c.getType())) return;
 		// Apply the client's visibility to the contact group
 		Group g = getContactGroup(c);
 		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
index b5a771970..5083da073 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java
@@ -588,6 +588,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility v) throws DbException {
+		if(!getApplicableContactTypes().contains(c.getType())) return;
 		// Apply the client's visibility to the contact group
 		Group g = getContactGroup(c);
 		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
@@ -600,6 +601,7 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
 	private void onPrivateGroupClientVisibilityChanging(Transaction txn,
 			Contact c, Visibility client) throws DbException {
 		try {
+			if(!getApplicableContactTypes().contains(c.getType())) return;
 			Collection<Group> shareables =
 					db.getGroups(txn, PrivateGroupManager.CLIENT_ID,
 							PrivateGroupManager.MAJOR_VERSION);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
index 7b9c81867..f80a38a97 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
@@ -510,6 +510,7 @@ abstract class SharingManagerImpl<S extends Shareable>
 	@Override
 	public void onClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility v) throws DbException {
+		if(!getApplicableContactTypes().contains(c.getType())) return;
 		// Apply the client's visibility to the contact group
 		Group g = getContactGroup(c);
 		db.setGroupVisibility(txn, c.getId(), g.getId(), v);
@@ -523,6 +524,7 @@ abstract class SharingManagerImpl<S extends Shareable>
 	private void onShareableClientVisibilityChanging(Transaction txn, Contact c,
 			Visibility client) throws DbException {
 		try {
+			if(!getApplicableContactTypes().contains(c.getType())) return;
 			Collection<Group> shareables = db.getGroups(txn,
 					getShareableClientId(), getShareableMajorVersion());
 			Map<GroupId, Visibility> m = getPreferredVisibilities(txn, c);
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
index b3161af64..341936e3c 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
@@ -119,6 +119,10 @@ public class IntroductionIntegrationTest
 		c2 = DaggerIntroductionIntegrationTestComponent.builder()
 				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
 		injectEagerSingletons(c2);
+
+		cMailbox = DaggerIntroductionIntegrationTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t3Dir)).build();
+		injectEagerSingletons(cMailbox);
 	}
 
 	@Test
diff --git a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
index c8e83d3cd..3d794f9eb 100644
--- a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
@@ -5,7 +5,6 @@ import net.jodah.concurrentunit.Waiter;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.contact.PrivateMailbox;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
@@ -14,6 +13,7 @@ import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -24,14 +24,14 @@ import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.TestDatabaseModule;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.introduction.IntroductionMessage;
-import org.briarproject.briar.api.introduction.IntroductionRequest;
-import org.briarproject.briar.api.introduction.IntroductionResponse;
 import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
-import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
 import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
 import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
 import org.briarproject.briar.api.mailbox.MailboxIntroductionManager;
+import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent;
+import org.briarproject.briar.api.mailbox.event.MailboxIntroductionResponseReceivedEvent;
 import org.briarproject.briar.test.BriarIntegrationTest;
+import org.briarproject.briar.test.BriarIntegrationTestComponent;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -42,8 +42,8 @@ import java.util.concurrent.TimeoutException;
 
 import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
 import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
-import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
 import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
+import static org.briarproject.briar.api.mailbox.MailboxIntroductionManager.CLIENT_ID;
 import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCEE_A;
@@ -54,8 +54,7 @@ import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-public class MailboxIntroductionIntegrationTest
-		extends
+public class MailboxIntroductionIntegrationTest extends
 		BriarIntegrationTest<MailboxIntroductionIntegrationTestComponent> {
 
 	// objects accessed from background threads need to be volatile
@@ -68,6 +67,7 @@ public class MailboxIntroductionIntegrationTest
 	private OwnerListener listener0;
 	private IntroduceeListener listener1;
 	private IntroduceeListener listener2;
+	private IntroduceeListener listenerMailbox;
 
 	/*
 	interface StateVisitor {
@@ -114,23 +114,34 @@ public class MailboxIntroductionIntegrationTest
 		injectEagerSingletons(cMailbox);
 	}
 
+	@Override
+	protected void injectEagerSingletons(
+			BriarIntegrationTestComponent component) {
+		super.injectEagerSingletons(component);
+		component.inject(new MailboxIntroductionModule.EagerSingletons());
+	}
+
 	@Test
 	public void testIntroductionSession() throws Exception {
-		addListeners(true, true);
+		addListeners(true, true, true);
 
 		// make introduction
 		long time = clock.currentTimeMillis();
 		Contact introducee = contact1From0;
 		PrivateMailbox mailbox = privateMailboxFrom0;
-		introductionManager0
-				.makeIntroduction(mailbox, introducee, time);
+		introductionManager0.makeIntroduction(mailbox, introducee, time);
 
 		// sync first REQUEST message
 		sync0ToMailbox(1, true);
 		eventWaiter.await(TIMEOUT, 1);
-		assertTrue(listener1.requestReceived);
-		assertEquals(mailbox.getAuthor().getName(),
-				listener1.getRequest().getName());
+		assertTrue(listenerMailbox.requestReceived);
+		assertEquals(authorMailbox.getId(), listenerMailbox.getRequest());
+		// sync first REQUEST message
+		syncMailboxTo0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+		assertEquals(authorMailbox.getId(), listener0.getAuthors()[0]);
+		assertEquals(author1.getId(), listener0.getAuthors()[1]);
 	}
 
 
@@ -139,16 +150,23 @@ public class MailboxIntroductionIntegrationTest
 		TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
 		TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
 		TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
+		TransportPropertyManager tpmMailbox =
+				cMailbox.getTransportPropertyManager();
 
 		tpm0.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
 		sync0To1(1, true);
 		sync0To2(1, true);
+		sync0ToMailbox(1, true);
 
 		tpm1.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
 		sync1To0(1, true);
 
 		tpm2.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
 		sync2To0(1, true);
+
+		tpmMailbox
+				.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
+		syncMailboxTo0(1, true);
 	}
 
 	private void assertMessagesAreAcked(
@@ -158,7 +176,8 @@ public class MailboxIntroductionIntegrationTest
 		}
 	}
 
-	private void addListeners(boolean accept1, boolean accept2) {
+	private void addListeners(boolean accept1, boolean accept2,
+			boolean accept3) {
 		// listen to events
 		listener0 = new OwnerListener();
 		c0.getEventBus().addListener(listener0);
@@ -166,6 +185,8 @@ public class MailboxIntroductionIntegrationTest
 		c1.getEventBus().addListener(listener1);
 		listener2 = new IntroduceeListener(2, accept2);
 		c2.getEventBus().addListener(listener2);
+		listenerMailbox = new IntroduceeListener(3, accept3);
+		cMailbox.getEventBus().addListener(listenerMailbox);
 	}
 
 	@MethodsNotNullByDefault
@@ -176,11 +197,15 @@ public class MailboxIntroductionIntegrationTest
 		protected volatile Event latestEvent;
 
 		@SuppressWarnings("WeakerAccess")
-		IntroductionResponse getResponse() {
+		AuthorId[] getAuthors() {
 			assertTrue(
-					latestEvent instanceof IntroductionResponseReceivedEvent);
-			return ((IntroductionResponseReceivedEvent) latestEvent)
-					.getIntroductionResponse();
+					latestEvent instanceof MailboxIntroductionResponseReceivedEvent);
+			AuthorId[] authors =
+					{((MailboxIntroductionResponseReceivedEvent) latestEvent)
+							.getFrom().getId(),
+							((MailboxIntroductionResponseReceivedEvent) latestEvent)
+									.getTo().getId()};
+			return authors;
 		}
 	}
 
@@ -203,14 +228,11 @@ public class MailboxIntroductionIntegrationTest
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof IntroductionRequestReceivedEvent) {
+			if (e instanceof MailboxIntroductionRequestReceivedEvent) {
 				latestEvent = e;
-				IntroductionRequestReceivedEvent introEvent =
-						((IntroductionRequestReceivedEvent) e);
+				MailboxIntroductionRequestReceivedEvent introEvent =
+						(MailboxIntroductionRequestReceivedEvent) e;
 				requestReceived = true;
-				IntroductionRequest ir = introEvent.getIntroductionRequest();
-				ContactId contactId = introEvent.getContactId();
-				sessionId = ir.getSessionId();
 				long time = clock.currentTimeMillis();
 				try {
 					if (introducee == 1 && answerRequests) {
@@ -245,11 +267,11 @@ public class MailboxIntroductionIntegrationTest
 			}
 		}
 
-		private IntroductionRequest getRequest() {
+		private AuthorId getRequest() {
 			assertTrue(
-					latestEvent instanceof IntroductionRequestReceivedEvent);
-			return ((IntroductionRequestReceivedEvent) latestEvent)
-					.getIntroductionRequest();
+					latestEvent instanceof MailboxIntroductionRequestReceivedEvent);
+			return ((MailboxIntroductionRequestReceivedEvent) latestEvent)
+					.getAuthorId();
 		}
 	}
 
@@ -261,28 +283,21 @@ public class MailboxIntroductionIntegrationTest
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof IntroductionResponseReceivedEvent) {
+			if (e instanceof MailboxIntroductionResponseReceivedEvent) {
 				latestEvent = e;
-				ContactId c =
-						((IntroductionResponseReceivedEvent) e)
-								.getContactId();
-				if (c.equals(contactId1From0)) {
+				AuthorId from = ((MailboxIntroductionResponseReceivedEvent) e)
+						.getFrom().getId();
+				if (from.equals(authorMailbox.getId())) {
 					response1Received = true;
-				} else if (c.equals(contactId2From0)) {
-					response2Received = true;
 				}
 				eventWaiter.resume();
-			} else if (e instanceof IntroductionAbortedEvent) {
-				latestEvent = e;
-				aborted = true;
-				eventWaiter.resume();
 			}
 		}
 
 	}
 
-	private void replacePreviousLocalMessageId(Author author,
-			BdfDictionary d, MessageId id) throws FormatException {
+	private void replacePreviousLocalMessageId(Author author, BdfDictionary d,
+			MessageId id) throws FormatException {
 		BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_A);
 		BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_B);
 		Author a1 = clientHelper
@@ -305,9 +320,8 @@ public class MailboxIntroductionIntegrationTest
 			Contact contact, MessageType type)
 			throws FormatException, DbException {
 		Group g = introductionManager0.getContactGroup(contact);
-		BdfDictionary query = BdfDictionary.of(
-				new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue())
-		);
+		BdfDictionary query = BdfDictionary
+				.of(new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()));
 		Map<MessageId, BdfDictionary> map =
 				ch.getMessageMetadataAsDictionary(g.getId(), query);
 		assertEquals(1, map.size());
@@ -323,8 +337,7 @@ public class MailboxIntroductionIntegrationTest
 		} else throw new AssertionError("Not implemented");
 	}
 
-	private OwnerSession getOwnerSession()
-			throws DbException, FormatException {
+	private OwnerSession getOwnerSession() throws DbException, FormatException {
 		Map<MessageId, BdfDictionary> dicts = c0.getClientHelper()
 				.getMessageMetadataAsDictionary(getLocalGroup().getId());
 		assertEquals(1, dicts.size());
diff --git a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTestComponent.java
index 80abea604..946855a73 100644
--- a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTestComponent.java
@@ -67,11 +67,11 @@ interface MailboxIntroductionIntegrationTestComponent
 
 	void inject(MailboxIntroductionIntegrationTest init);
 
-	MessageEncoder getMessageEncoder();
+	MailboxMessageEncoder getMessageEncoder();
 
-	MessageParser getMessageParser();
+	MailboxMessageParser getMessageParser();
 
-	SessionParser getSessionParser();
+	MailboxSessionParser getSessionParser();
 
 	MailboxIntroductionCrypto getMailboxIntroductionCrypto();
 
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
index 7c43ea31a..394d19204 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
@@ -187,7 +187,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		component.inject(new GroupInvitationModule.EagerSingletons());
 		component.inject(new IdentityModule.EagerSingletons());
 		component.inject(new IntroductionModule.EagerSingletons());
-		component.inject(new MailboxIntroductionModule.EagerSingletons());
+	//	component.inject(new MailboxIntroductionModule.EagerSingletons());
 		component.inject(new LifecycleModule.EagerSingletons());
 		component.inject(new MessagingModule.EagerSingletons());
 		component.inject(new PrivateGroupModule.EagerSingletons());
-- 
GitLab