From 4d618644d61a28d414cda4701efccc0a7b2918f3 Mon Sep 17 00:00:00 2001
From: Julian Dehm <goapunk@riseup.net>
Date: Thu, 6 Sep 2018 11:45:13 +0200
Subject: [PATCH] update intro

---
 .../briar/mailbox/AbstractProtocolEngine.java | 279 ++++++-----
 .../briar/mailbox/IntroduceeSession.java      | 436 +++++++++---------
 .../briar/mailbox/IntroductionConstants.java  |  48 ++
 .../briar/mailbox/MessageEncoder.java         |  47 +-
 .../briar/mailbox/MessageEncoderImpl.java     | 304 ++++++------
 .../briar/mailbox/OwnerProtocolEngine.java    | 111 ++++-
 .../briar/mailbox/OwnerSession.java           |  63 ++-
 .../briar/mailbox/OwnerState.java             |   2 +-
 .../briar/mailbox/ProtocolEngine.java         |  33 +-
 9 files changed, 725 insertions(+), 598 deletions(-)
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java

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 dbd246453..07231a624 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
@@ -35,146 +35,143 @@ import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 @Immutable
 @NotNullByDefault
 abstract class AbstractProtocolEngine<S extends Session>
-		implements ProtocolEngine<S> {
-
-	protected final DatabaseComponent db;
-	protected final ClientHelper clientHelper;
-	protected final ContactManager contactManager;
-	protected final ContactGroupFactory contactGroupFactory;
-	protected final MessageTracker messageTracker;
-	protected final IdentityManager identityManager;
-	protected final MessageParser messageParser;
-	protected final MessageEncoder messageEncoder;
-	protected final Clock clock;
-
-	AbstractProtocolEngine(
-			DatabaseComponent db,
-			ClientHelper clientHelper,
-			ContactManager contactManager,
-			ContactGroupFactory contactGroupFactory,
-			MessageTracker messageTracker,
-			IdentityManager identityManager,
-			MessageParser messageParser,
-			MessageEncoder messageEncoder,
-			Clock clock) {
-		this.db = db;
-		this.clientHelper = clientHelper;
-		this.contactManager = contactManager;
-		this.contactGroupFactory = contactGroupFactory;
-		this.messageTracker = messageTracker;
-		this.identityManager = identityManager;
-		this.messageParser = messageParser;
-		this.messageEncoder = messageEncoder;
-		this.clock = clock;
-	}
-
-	Message sendMailboxRequestMessage(Transaction txn, PeerSession s,
-			long timestamp, Author author, @Nullable String message)
-			throws DbException {
-		Message m = messageEncoder
-				.encodeRequestMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), author, message);
-		sendMessage(txn, MAILBOX_REQUEST, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendMailboxAcceptMessage(Transaction txn, PeerSession s,
-			long timestamp,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties,
-			boolean visible) throws DbException {
-		Message m = messageEncoder
-				.encodeAcceptMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId(),
-						ephemeralPublicKey, acceptTimestamp,
-						transportProperties);
-		sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendContactRequestMessage(Transaction txn, PeerSession s,
-			long timestamp, Author author, @Nullable String message)
-			throws DbException {
-		Message m = messageEncoder
-				.encodeRequestMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), author, message);
-		sendMessage(txn, CONTACT_REQUEST, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendContactAcceptMessage(Transaction txn, PeerSession s,
-			long timestamp,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties,
-			boolean visible) throws DbException {
-		Message m = messageEncoder
-				.encodeAcceptMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId(),
-						ephemeralPublicKey, acceptTimestamp,
-						transportProperties);
-		sendMessage(txn, CONTACT_ACCEPT, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendMailboxInfotMessage(Transaction txn, PeerSession s,
-			long timestamp,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties,
-			boolean visible) throws DbException {
-		Message m = messageEncoder
-				.encodeAcceptMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId(),
-						ephemeralPublicKey, acceptTimestamp,
-						transportProperties);
-		sendMessage(txn, MAILBOX_INFO, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
-			boolean visible) throws DbException {
-		Message m = messageEncoder
-				.encodeDeclineMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId());
-		sendMessage(txn, DECLINE, s.getSessionId(), m);
-		return m;
-	}
-
-	Message sendAbortMessage(Transaction txn, PeerSession s, long timestamp)
-			throws DbException {
-		Message m = messageEncoder
-				.encodeAbortMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId());
-		sendMessage(txn, ABORT, s.getSessionId(), m);
-		return m;
-	}
-
-	private void sendMessage(Transaction txn, MessageType type,
-			SessionId sessionId, Message m)
-			throws DbException {
-		BdfDictionary meta = messageEncoder
-				.encodeMetadata(type, sessionId, m.getTimestamp(), true, true);
-		try {
-			clientHelper.addLocalMessage(txn, m, meta, true);
-		} catch (FormatException e) {
-			throw new AssertionError(e);
-		}
-	}
-
-	boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId,
-			@Nullable MessageId dependency) {
-		if (dependency == null) return lastRemoteMessageId != null;
-		return lastRemoteMessageId == null ||
-				!dependency.equals(lastRemoteMessageId);
-	}
-
-	long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
-		return Math.max(
-				clock.currentTimeMillis(),
-				Math.max(
-						localTimestamp,
-						requestTimestamp
-				) + 1
-		);
-	}
+        implements ProtocolEngine<S> {
+
+    protected final DatabaseComponent db;
+    protected final ClientHelper clientHelper;
+    protected final ContactManager contactManager;
+    protected final ContactGroupFactory contactGroupFactory;
+    protected final MessageTracker messageTracker;
+    protected final IdentityManager identityManager;
+    protected final MessageParser messageParser;
+    protected final MessageEncoder messageEncoder;
+    protected final Clock clock;
+
+    AbstractProtocolEngine(
+            DatabaseComponent db,
+            ClientHelper clientHelper,
+            ContactManager contactManager,
+            ContactGroupFactory contactGroupFactory,
+            MessageTracker messageTracker,
+            IdentityManager identityManager,
+            MessageParser messageParser,
+            MessageEncoder messageEncoder,
+            Clock clock) {
+        this.db = db;
+        this.clientHelper = clientHelper;
+        this.contactManager = contactManager;
+        this.contactGroupFactory = contactGroupFactory;
+        this.messageTracker = messageTracker;
+        this.identityManager = identityManager;
+        this.messageParser = messageParser;
+        this.messageEncoder = messageEncoder;
+        this.clock = clock;
+    }
+
+    Message sendMailboxRequestMessage(Transaction txn, PeerSession s, long timestamp) throws DbException {
+        Message m = messageEncoder
+                .encodeRequestMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId());
+        sendMessage(txn, MAILBOX_REQUEST, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendMailboxAcceptMessage(Transaction txn, PeerSession s,
+                                     long timestamp,
+                                     byte[] ephemeralPublicKey, long acceptTimestamp,
+                                     Map<TransportId, TransportProperties> transportProperties,
+                                     boolean visible) throws DbException {
+        Message m = messageEncoder
+                .encodeAcceptMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), s.getSessionId(),
+                        ephemeralPublicKey, acceptTimestamp,
+                        transportProperties);
+        sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendContactRequestMessage(Transaction txn, PeerSession s,
+                                      long timestamp, Author author, @Nullable String message)
+            throws DbException {
+        Message m = messageEncoder
+                .encodeRequestMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), author, message);
+        sendMessage(txn, CONTACT_REQUEST, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendContactAcceptMessage(Transaction txn, PeerSession s,
+                                     long timestamp,
+                                     byte[] ephemeralPublicKey, long acceptTimestamp,
+                                     Map<TransportId, TransportProperties> transportProperties,
+                                     boolean visible) throws DbException {
+        Message m = messageEncoder
+                .encodeAcceptMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), s.getSessionId(),
+                        ephemeralPublicKey, acceptTimestamp,
+                        transportProperties);
+        sendMessage(txn, CONTACT_ACCEPT, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendMailboxInfotMessage(Transaction txn, PeerSession s,
+                                    long timestamp,
+                                    byte[] ephemeralPublicKey, long acceptTimestamp,
+                                    Map<TransportId, TransportProperties> transportProperties,
+                                    boolean visible) throws DbException {
+        Message m = messageEncoder
+                .encodeAcceptMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), s.getSessionId(),
+                        ephemeralPublicKey, acceptTimestamp,
+                        transportProperties);
+        sendMessage(txn, MAILBOX_INFO, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp,
+                               boolean visible) throws DbException {
+        Message m = messageEncoder
+                .encodeDeclineMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), s.getSessionId());
+        sendMessage(txn, DECLINE, s.getSessionId(), m);
+        return m;
+    }
+
+    Message sendAbortMessage(Transaction txn, PeerSession s, long timestamp)
+            throws DbException {
+        Message m = messageEncoder
+                .encodeAbortMessage(s.getContactGroupId(), timestamp,
+                        s.getLastLocalMessageId(), s.getSessionId());
+        sendMessage(txn, ABORT, s.getSessionId(), m);
+        return m;
+    }
+
+    private void sendMessage(Transaction txn, MessageType type,
+                             SessionId sessionId, Message m)
+            throws DbException {
+        BdfDictionary meta = messageEncoder
+                .encodeMetadata(type, sessionId, m.getTimestamp(), true, true);
+        try {
+            clientHelper.addLocalMessage(txn, m, meta, true);
+        } catch (FormatException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId,
+                                @Nullable MessageId dependency) {
+        if (dependency == null) return lastRemoteMessageId != null;
+        return lastRemoteMessageId == null ||
+                !dependency.equals(lastRemoteMessageId);
+    }
+
+    long getLocalTimestamp(long localTimestamp, long requestTimestamp) {
+        return Math.max(
+                clock.currentTimeMillis(),
+                Math.max(
+                        localTimestamp,
+                        requestTimestamp
+                ) + 1
+        );
+    }
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
index 93fcf7130..7f8b5b60a 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
@@ -23,224 +23,222 @@ import static org.briarproject.briar.mailbox.IntroduceeState.START;
 
 @Immutable
 @NotNullByDefault
-class IntroduceeSession
-		extends Session<org.briarproject.briar.mailbox.IntroduceeState>
-		implements PeerSession {
-
-	private final GroupId contactGroupId;
-	private final Author introducer;
-	private final Local local;
-	private final Remote remote;
-	@Nullable
-	private final byte[] masterKey;
-	@Nullable
-	private final Map<TransportId, KeySetId> transportKeys;
-
-	IntroduceeSession(SessionId sessionId, IntroduceeState state,
-			long requestTimestamp, GroupId contactGroupId, Author introducer,
-			Local local, Remote remote, @Nullable byte[] masterKey,
-			@Nullable Map<TransportId, KeySetId> transportKeys,
-			long sessionCounter) {
-		super(sessionId, state, requestTimestamp, sessionCounter);
-		this.contactGroupId = contactGroupId;
-		this.introducer = introducer;
-		this.local = local;
-		this.remote = remote;
-		this.masterKey = masterKey;
-		this.transportKeys = transportKeys;
-	}
-
-	static IntroduceeSession getInitial(GroupId contactGroupId,
-			SessionId sessionId, Author introducer, boolean localIsAlice,
-			Author remoteAuthor) {
-		Local local =
-				new Local(localIsAlice, null, -1, null, null, -1, null);
-		Remote remote =
-				new Remote(!localIsAlice, remoteAuthor, null, null, null, -1,
-						null);
-		return new IntroduceeSession(sessionId, START, -1, contactGroupId,
-				introducer, local, remote, null, null, 0);
-	}
-
-	static IntroduceeSession addRemoteRequest(
-			IntroduceeSession 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);
-		return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(),
-				s.contactGroupId, s.introducer, local, remote, s.masterKey,
-				s.transportKeys, s.getSessionCounter());
-	}
-
-	static IntroduceeSession addLocalAuth(
-			IntroduceeSession 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,
-				s.local.acceptTimestamp,
-				s.local.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
-		// just add the mac key
-		Remote remote = new Remote(s.remote.alice, s.remote.author,
-				s.remote.lastMessageId, s.remote.ephemeralPublicKey,
-				s.remote.transportProperties, s.remote.acceptTimestamp,
-				s.remote.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
-		// add master key
-		return new IntroduceeSession(s.getSessionId(), state,
-				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
-				remote, masterKey.getBytes(), s.transportKeys,
-				s.getSessionCounter());
-	}
-
-	static IntroduceeSession awaitAuth(
-			IntroduceeSession 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 IntroduceeSession(s.getSessionId(), AWAIT_REMOTE_RESPONSE,
-				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
-				remote, null, transportKeys, s.getSessionCounter());
-	}
-
-	static IntroduceeSession clear(
-			IntroduceeSession s, IntroduceeState state,
-			@Nullable MessageId lastLocalMessageId, long localTimestamp,
-			@Nullable MessageId lastRemoteMessageId) {
-		Local local =
-				new Local(s.local.alice, lastLocalMessageId, localTimestamp,
-						null, null, -1, null);
-		Remote remote =
-				new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId,
-						null, null, -1, null);
-		return new IntroduceeSession(s.getSessionId(), state,
-				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
-				remote, null, null, s.getSessionCounter());
-	}
-
-	@Override
-	Role getRole() {
-		return INTRODUCEE;
-	}
-
-	@Override
-	public GroupId getContactGroupId() {
-		return contactGroupId;
-	}
-
-	@Override
-	public long getLocalTimestamp() {
-		return local.lastMessageTimestamp;
-	}
-
-	@Nullable
-	@Override
-	public MessageId getLastLocalMessageId() {
-		return local.lastMessageId;
-	}
-
-	@Nullable
-	@Override
-	public MessageId getLastRemoteMessageId() {
-		return remote.lastMessageId;
-	}
-
-	Author getIntroducer() {
-		return introducer;
-	}
-
-	public Local getLocal() {
-		return local;
-	}
-
-	public Remote getRemote() {
-		return remote;
-	}
-
-	@Nullable
-	byte[] getMasterKey() {
-		return masterKey;
-	}
-
-	@Nullable
-	Map<TransportId, KeySetId> getTransportKeys() {
-		return transportKeys;
-	}
-
-	abstract static class Common {
-		final boolean alice;
-		@Nullable
-		final MessageId lastMessageId;
-		@Nullable
-		final byte[] ephemeralPublicKey;
-
-		final long acceptTimestamp;
-		@Nullable
-		final byte[] macKey;
-
-		private Common(boolean alice, @Nullable MessageId lastMessageId,
-				@Nullable byte[] ephemeralPublicKey, @Nullable
-				long acceptTimestamp, @Nullable byte[] macKey) {
-			this.alice = alice;
-			this.lastMessageId = lastMessageId;
-			this.ephemeralPublicKey = ephemeralPublicKey;
-			this.acceptTimestamp = acceptTimestamp;
-			this.macKey = macKey;
-		}
-	}
-
-	static class Local extends Common {
-		final long lastMessageTimestamp;
-		@Nullable
-		final byte[] ephemeralPrivateKey;
-
-		Local(boolean alice, @Nullable MessageId lastMessageId,
-				long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey,
-				@Nullable byte[] ephemeralPrivateKey, long acceptTimestamp,
-				@Nullable byte[] macKey) {
-			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
-					macKey);
-			this.lastMessageTimestamp = lastMessageTimestamp;
-			this.ephemeralPrivateKey = ephemeralPrivateKey;
-		}
-
-		private Local(Local s, @Nullable MessageId lastMessageId,
-				long lastMessageTimestamp) {
-			this(s.alice, lastMessageId, lastMessageTimestamp,
-					s.ephemeralPublicKey, s.ephemeralPrivateKey,
-					s.acceptTimestamp, s.macKey);
-		}
-	}
-
-	static class Remote extends Common {
-		final Author author;
-		@Nullable
-		final Map<TransportId, TransportProperties> transportProperties;
-
-		Remote(boolean alice, Author author,
-				@Nullable MessageId lastMessageId,
-				@Nullable byte[] ephemeralPublicKey, @Nullable
-				Map<TransportId, TransportProperties> transportProperties,
-				long acceptTimestamp, @Nullable byte[] macKey) {
-			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
-					macKey);
-			this.author = author;
-			this.transportProperties = transportProperties;
-		}
-
-		private Remote(Remote s, @Nullable MessageId lastMessageId) {
-			this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
-					s.transportProperties, s.acceptTimestamp, s.macKey);
-		}
-
-	}
+class IntroduceeSession extends Session<IntroduceeState> implements PeerSession {
+
+    private final GroupId contactGroupId;
+    private final Author introducer;
+    private final Local local;
+    private final Remote remote;
+    @Nullable
+    private final byte[] masterKey;
+    @Nullable
+    private final Map<TransportId, KeySetId> transportKeys;
+
+    IntroduceeSession(SessionId sessionId, IntroduceeState state,
+                      long requestTimestamp, GroupId contactGroupId, Author introducer,
+                      Local local, Remote remote, @Nullable byte[] masterKey,
+                      @Nullable Map<TransportId, KeySetId> transportKeys,
+                      long sessionCounter) {
+        super(sessionId, state, requestTimestamp, sessionCounter);
+        this.contactGroupId = contactGroupId;
+        this.introducer = introducer;
+        this.local = local;
+        this.remote = remote;
+        this.masterKey = masterKey;
+        this.transportKeys = transportKeys;
+    }
+
+    static IntroduceeSession getInitial(GroupId contactGroupId,
+                                        SessionId sessionId, Author introducer, boolean localIsAlice,
+                                        Author remoteAuthor) {
+        Local local =
+                new Local(localIsAlice, null, -1, null, null, -1, null);
+        Remote remote =
+                new Remote(!localIsAlice, remoteAuthor, null, null, null, -1,
+                        null);
+        return new IntroduceeSession(sessionId, START, -1, contactGroupId,
+                introducer, local, remote, null, null, 0);
+    }
+
+    static IntroduceeSession addRemoteRequest(
+            IntroduceeSession 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);
+        return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(),
+                s.contactGroupId, s.introducer, local, remote, s.masterKey,
+                s.transportKeys, s.getSessionCounter());
+    }
+
+    static IntroduceeSession addLocalAuth(
+            IntroduceeSession 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,
+                s.local.acceptTimestamp,
+                s.local.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
+        // just add the mac key
+        Remote remote = new Remote(s.remote.alice, s.remote.author,
+                s.remote.lastMessageId, s.remote.ephemeralPublicKey,
+                s.remote.transportProperties, s.remote.acceptTimestamp,
+                s.remote.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
+        // add master key
+        return new IntroduceeSession(s.getSessionId(), state,
+                s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+                remote, masterKey.getBytes(), s.transportKeys,
+                s.getSessionCounter());
+    }
+
+    static IntroduceeSession awaitAuth(
+            IntroduceeSession 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 IntroduceeSession(s.getSessionId(), AWAIT_REMOTE_RESPONSE,
+                s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+                remote, null, transportKeys, s.getSessionCounter());
+    }
+
+    static IntroduceeSession clear(
+            IntroduceeSession s, IntroduceeState state,
+            @Nullable MessageId lastLocalMessageId, long localTimestamp,
+            @Nullable MessageId lastRemoteMessageId) {
+        Local local =
+                new Local(s.local.alice, lastLocalMessageId, localTimestamp,
+                        null, null, -1, null);
+        Remote remote =
+                new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId,
+                        null, null, -1, null);
+        return new IntroduceeSession(s.getSessionId(), state,
+                s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+                remote, null, null, s.getSessionCounter());
+    }
+
+    @Override
+    Role getRole() {
+        return INTRODUCEE;
+    }
+
+    @Override
+    public GroupId getContactGroupId() {
+        return contactGroupId;
+    }
+
+    @Override
+    public long getLocalTimestamp() {
+        return local.lastMessageTimestamp;
+    }
+
+    @Nullable
+    @Override
+    public MessageId getLastLocalMessageId() {
+        return local.lastMessageId;
+    }
+
+    @Nullable
+    @Override
+    public MessageId getLastRemoteMessageId() {
+        return remote.lastMessageId;
+    }
+
+    Author getIntroducer() {
+        return introducer;
+    }
+
+    public Local getLocal() {
+        return local;
+    }
+
+    public Remote getRemote() {
+        return remote;
+    }
+
+    @Nullable
+    byte[] getMasterKey() {
+        return masterKey;
+    }
+
+    @Nullable
+    Map<TransportId, KeySetId> getTransportKeys() {
+        return transportKeys;
+    }
+
+    abstract static class Common {
+        final boolean alice;
+        @Nullable
+        final MessageId lastMessageId;
+        @Nullable
+        final byte[] ephemeralPublicKey;
+
+        final long acceptTimestamp;
+        @Nullable
+        final byte[] macKey;
+
+        private Common(boolean alice, @Nullable MessageId lastMessageId,
+                       @Nullable byte[] ephemeralPublicKey, @Nullable
+                               long acceptTimestamp, @Nullable byte[] macKey) {
+            this.alice = alice;
+            this.lastMessageId = lastMessageId;
+            this.ephemeralPublicKey = ephemeralPublicKey;
+            this.acceptTimestamp = acceptTimestamp;
+            this.macKey = macKey;
+        }
+    }
+
+    static class Local extends Common {
+        final long lastMessageTimestamp;
+        @Nullable
+        final byte[] ephemeralPrivateKey;
+
+        Local(boolean alice, @Nullable MessageId lastMessageId,
+              long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey,
+              @Nullable byte[] ephemeralPrivateKey, long acceptTimestamp,
+              @Nullable byte[] macKey) {
+            super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
+                    macKey);
+            this.lastMessageTimestamp = lastMessageTimestamp;
+            this.ephemeralPrivateKey = ephemeralPrivateKey;
+        }
+
+        private Local(Local s, @Nullable MessageId lastMessageId,
+                      long lastMessageTimestamp) {
+            this(s.alice, lastMessageId, lastMessageTimestamp,
+                    s.ephemeralPublicKey, s.ephemeralPrivateKey,
+                    s.acceptTimestamp, s.macKey);
+        }
+    }
+
+    static class Remote extends Common {
+        final Author author;
+        @Nullable
+        final Map<TransportId, TransportProperties> transportProperties;
+
+        Remote(boolean alice, Author author,
+               @Nullable MessageId lastMessageId,
+               @Nullable byte[] ephemeralPublicKey, @Nullable
+                       Map<TransportId, TransportProperties> transportProperties,
+               long acceptTimestamp, @Nullable byte[] macKey) {
+            super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
+                    macKey);
+            this.author = author;
+            this.transportProperties = transportProperties;
+        }
+
+        private Remote(Remote s, @Nullable MessageId lastMessageId) {
+            this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
+                    s.transportProperties, s.acceptTimestamp, s.macKey);
+        }
+
+    }
 
 }
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
new file mode 100644
index 000000000..940295983
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java
@@ -0,0 +1,48 @@
+package org.briarproject.briar.mailbox;
+
+interface IntroductionConstants {
+
+	// Group metadata keys
+	String GROUP_KEY_CONTACT_ID = "contactId";
+
+	// Message metadata keys
+	String MSG_KEY_MESSAGE_TYPE = "messageType";
+	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_AVAILABLE_TO_ANSWER = "availableToAnswer";
+
+	// Session Keys
+	String SESSION_KEY_SESSION_ID = "sessionId";
+	String SESSION_KEY_ROLE = "role";
+	String SESSION_KEY_STATE = "state";
+	String SESSION_KEY_REQUEST_TIMESTAMP = "requestTimestamp";
+	String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
+	String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
+	String SESSION_KEY_LAST_REMOTE_MESSAGE_ID = "lastRemoteMessageId";
+
+	// Session Keys Introducer
+	String SESSION_KEY_INTRODUCEE_A = "introduceeA";
+	String SESSION_KEY_INTRODUCEE_B = "introduceeB";
+	String SESSION_KEY_GROUP_ID = "groupId";
+	String SESSION_KEY_AUTHOR = "author";
+
+	// Session Keys Introducee
+	String SESSION_KEY_INTRODUCER = "introducer";
+	String SESSION_KEY_LOCAL = "local";
+	String SESSION_KEY_REMOTE = "remote";
+
+	String SESSION_KEY_MASTER_KEY = "masterKey";
+	String SESSION_KEY_TRANSPORT_KEYS = "transportKeys";
+
+	String SESSION_KEY_ALICE = "alice";
+	String SESSION_KEY_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey";
+	String SESSION_KEY_EPHEMERAL_PRIVATE_KEY = "ephemeralPrivateKey";
+	String SESSION_KEY_TRANSPORT_PROPERTIES = "transportProperties";
+	String SESSION_KEY_ACCEPT_TIMESTAMP = "acceptTimestamp";
+	String SESSION_KEY_MAC_KEY = "macKey";
+
+	String SESSION_KEY_REMOTE_AUTHOR = "remoteAuthor";
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java
index 2bf584c98..28ac43793 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoder.java
@@ -1,7 +1,6 @@
 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;
@@ -9,7 +8,6 @@ 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 org.briarproject.briar.introduction.MessageType;
 
 import java.util.Map;
 
@@ -18,39 +16,36 @@ import javax.annotation.Nullable;
 @NotNullByDefault
 interface MessageEncoder {
 
-	BdfDictionary encodeRequestMetadata(long timestamp);
+    BdfDictionary encodeRequestMetadata(long timestamp);
 
-	BdfDictionary encodeMetadata(MessageType type,
-			@Nullable SessionId sessionId, long timestamp, boolean local,
-			boolean read, boolean visible);
+    BdfDictionary encodeMetadata(MessageType type, @Nullable SessionId sessionId, long timestamp, boolean local);
 
-	void addSessionId(BdfDictionary meta, SessionId sessionId);
+    void addSessionId(BdfDictionary meta, SessionId sessionId);
 
-	void setVisibleInUi(BdfDictionary meta, boolean visible);
+    void setVisibleInUi(BdfDictionary meta, boolean visible);
 
-	void setAvailableToAnswer(BdfDictionary meta, boolean available);
+    void setAvailableToAnswer(BdfDictionary meta, boolean available);
 
-	Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, Author author,
-			@Nullable String message);
+    Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
+                                 @Nullable MessageId previousMessageId);
 
-	Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties);
+    Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
+                                @Nullable MessageId previousMessageId, SessionId sessionId,
+                                byte[] ephemeralPublicKey, long acceptTimestamp,
+                                Map<TransportId, TransportProperties> transportProperties);
 
-	Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId);
+    Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
+                                 @Nullable MessageId previousMessageId, SessionId sessionId);
 
-	Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] mac, byte[] signature);
+    Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
+                              @Nullable MessageId previousMessageId, SessionId sessionId,
+                              byte[] mac, byte[] signature);
 
-	Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] mac);
+    Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
+                                  @Nullable MessageId previousMessageId, SessionId sessionId,
+                                  byte[] mac);
 
-	Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId);
+    Message encodeAbortMessage(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/MessageEncoderImpl.java
index a75d0ba27..e71a481c8 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageEncoderImpl.java
@@ -4,7 +4,6 @@ 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;
@@ -13,173 +12,158 @@ 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 org.briarproject.briar.introduction.MessageType;
 
 import java.util.Map;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
-import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
-import static org.briarproject.briar.introduction.MessageType.ABORT;
-import static org.briarproject.briar.introduction.MessageType.ACCEPT;
-import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
-import static org.briarproject.briar.introduction.MessageType.AUTH;
-import static org.briarproject.briar.introduction.MessageType.DECLINE;
-import static org.briarproject.briar.introduction.MessageType.REQUEST;
+import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
+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.DECLINE;
+import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 
 @NotNullByDefault
 class MessageEncoderImpl implements
-		MessageEncoder {
-
-	private final ClientHelper clientHelper;
-	private final MessageFactory messageFactory;
-
-	@Inject
-	MessageEncoderImpl(ClientHelper clientHelper,
-			MessageFactory messageFactory) {
-		this.clientHelper = clientHelper;
-		this.messageFactory = messageFactory;
-	}
-
-	@Override
-	public BdfDictionary encodeRequestMetadata(long timestamp) {
-		BdfDictionary meta =
-				encodeMetadata(REQUEST, null, timestamp, false, false, false);
-		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
-		return meta;
-	}
-
-	@Override
-	public BdfDictionary encodeMetadata(MessageType type,
-			@Nullable SessionId sessionId, long timestamp, boolean local,
-			boolean read, boolean visible) {
-		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 != REQUEST)
-			throw new IllegalArgumentException();
-		meta.put(MSG_KEY_TIMESTAMP, timestamp);
-		meta.put(MSG_KEY_LOCAL, local);
-		meta.put(MSG_KEY_READ, read);
-		meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
-		return meta;
-	}
-
-	@Override
-	public void addSessionId(BdfDictionary meta, SessionId sessionId) {
-		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);
-	}
-
-	@Override
-	public Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, Author author,
-			@Nullable String message) {
-		if (message != null && message.equals("")) {
-			throw new IllegalArgumentException();
-		}
-		BdfList body = BdfList.of(
-				REQUEST.getValue(),
-				previousMessageId,
-				clientHelper.toList(author),
-				message
-		);
-		return createMessage(contactGroupId, timestamp, body);
-	}
-
-	@Override
-	public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] ephemeralPublicKey, long acceptTimestamp,
-			Map<TransportId, TransportProperties> transportProperties) {
-		BdfList body = BdfList.of(
-				ACCEPT.getValue(),
-				sessionId,
-				previousMessageId,
-				ephemeralPublicKey,
-				acceptTimestamp,
-				clientHelper.toDictionary(transportProperties)
-		);
-		return createMessage(contactGroupId, timestamp, body);
-	}
-
-	@Override
-	public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId) {
-		return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp,
-				previousMessageId);
-	}
-
-	@Override
-	public Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] mac, byte[] signature) {
-		BdfList body = BdfList.of(
-				AUTH.getValue(),
-				sessionId,
-				previousMessageId,
-				mac,
-				signature
-		);
-		return createMessage(contactGroupId, timestamp, body);
-	}
-
-	@Override
-	public Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId,
-			byte[] mac) {
-		BdfList body = BdfList.of(
-				ACTIVATE.getValue(),
-				sessionId,
-				previousMessageId,
-				mac
-		);
-		return createMessage(contactGroupId, timestamp, body);
-	}
-
-	@Override
-	public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId) {
-		return encodeMessage(ABORT, contactGroupId, sessionId, timestamp,
-				previousMessageId);
-	}
-
-	private Message encodeMessage(MessageType type, GroupId contactGroupId,
-			SessionId sessionId, long timestamp,
-			@Nullable MessageId previousMessageId) {
-		BdfList body = BdfList.of(
-				type.getValue(),
-				sessionId,
-				previousMessageId
-		);
-		return createMessage(contactGroupId, timestamp, body);
-	}
-
-	private Message createMessage(GroupId contactGroupId, long timestamp,
-			BdfList body) {
-		try {
-			return messageFactory.createMessage(contactGroupId, timestamp,
-					clientHelper.toByteArray(body));
-		} catch (FormatException e) {
-			throw new AssertionError(e);
-		}
-	}
+        MessageEncoder {
+
+    private final ClientHelper clientHelper;
+    private final MessageFactory messageFactory;
+
+    @Inject
+    MessageEncoderImpl(ClientHelper clientHelper,
+                       MessageFactory messageFactory) {
+        this.clientHelper = clientHelper;
+        this.messageFactory = messageFactory;
+    }
+
+    @Override
+    public BdfDictionary encodeRequestMetadata(long timestamp) {
+        BdfDictionary meta =
+                encodeMetadata(MAILBOX_REQUEST, null, timestamp, false);
+        meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
+        return meta;
+    }
+
+    @Override
+    public BdfDictionary encodeMetadata(MessageType type,
+                                        @Nullable SessionId sessionId, long timestamp, boolean local) {
+        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();
+        meta.put(MSG_KEY_TIMESTAMP, timestamp);
+        meta.put(MSG_KEY_LOCAL, local);
+        return meta;
+    }
+
+    @Override
+    public void addSessionId(BdfDictionary meta, SessionId sessionId) {
+        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);
+    }
+
+    @Override
+    public Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
+                                        @Nullable MessageId previousMessageId) {
+        BdfList body = BdfList.of(
+                MAILBOX_REQUEST.getValue(),
+                previousMessageId
+        );
+        return createMessage(contactGroupId, timestamp, body);
+    }
+
+    @Override
+    public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp,
+                                       @Nullable MessageId previousMessageId, SessionId sessionId,
+                                       byte[] ephemeralPublicKey, long acceptTimestamp,
+                                       Map<TransportId, TransportProperties> transportProperties) {
+        BdfList body = BdfList.of(
+                ACCEPT.getValue(),
+                sessionId,
+                previousMessageId,
+                ephemeralPublicKey,
+                acceptTimestamp,
+                clientHelper.toDictionary(transportProperties)
+        );
+        return createMessage(contactGroupId, timestamp, body);
+    }
+
+    @Override
+    public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
+                                        @Nullable MessageId previousMessageId, SessionId sessionId) {
+        return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp,
+                previousMessageId);
+    }
+
+    @Override
+    public Message encodeAuthMessage(GroupId contactGroupId, long timestamp,
+                                     @Nullable MessageId previousMessageId, SessionId sessionId,
+                                     byte[] mac, byte[] signature) {
+        BdfList body = BdfList.of(
+                AUTH.getValue(),
+                sessionId,
+                previousMessageId,
+                mac,
+                signature
+        );
+        return createMessage(contactGroupId, timestamp, body);
+    }
+
+    @Override
+    public Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
+                                         @Nullable MessageId previousMessageId, SessionId sessionId,
+                                         byte[] mac) {
+        BdfList body = BdfList.of(
+                ACTIVATE.getValue(),
+                sessionId,
+                previousMessageId,
+                mac
+        );
+        return createMessage(contactGroupId, timestamp, body);
+    }
+
+    @Override
+    public Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
+                                      @Nullable MessageId previousMessageId, SessionId sessionId) {
+        return encodeMessage(ABORT, contactGroupId, sessionId, timestamp,
+                previousMessageId);
+    }
+
+    private Message encodeMessage(MessageType type, GroupId contactGroupId,
+                                  SessionId sessionId, long timestamp,
+                                  @Nullable MessageId previousMessageId) {
+        BdfList body = BdfList.of(
+                type.getValue(),
+                sessionId,
+                previousMessageId
+        );
+        return createMessage(contactGroupId, timestamp, body);
+    }
+
+    private Message createMessage(GroupId contactGroupId, long timestamp,
+                                  BdfList body) {
+        try {
+            return messageFactory.createMessage(contactGroupId, timestamp,
+                    clientHelper.toByteArray(body));
+        } catch (FormatException e) {
+            throw new AssertionError(e);
+        }
+    }
 
 }
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 384f5b032..5cf7feaf3 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
@@ -1,4 +1,113 @@
 package org.briarproject.briar.mailbox;
 
-class OwnerProtocolEngine extends AbstractProtocolEngine {
+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.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.sync.Message;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.api.client.MessageTracker;
+import org.briarproject.briar.api.client.ProtocolStateException;
+
+import javax.inject.Inject;
+
+import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_M;
+
+class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
+
+    @Inject
+    OwnerProtocolEngine(
+            DatabaseComponent db,
+            ClientHelper clientHelper,
+            ContactManager contactManager,
+            ContactGroupFactory contactGroupFactory,
+            MessageTracker messageTracker,
+            IdentityManager identityManager,
+            MessageParser messageParser,
+            MessageEncoder messageEncoder,
+            Clock clock) {
+        super(db, clientHelper, contactManager, contactGroupFactory,
+                messageTracker, identityManager, messageParser, messageEncoder,
+                clock);
+    }
+
+    @Override
+    public OwnerSession onRequestAction(Transaction txn, OwnerSession s, long timestamp)
+            throws DbException {
+        switch (s.getState()) {
+            case START:
+                return onLocalRequest(txn, s, timestamp);
+            case AWAIT_RESPONSE_M:
+            case M_DECLINED:
+            case AWAIT_RESPONSE_B:
+            case B_DECLINED:
+            case AWAIT_AUTH_M:
+            case ADDED:
+                throw new ProtocolStateException(); // Invalid in these states
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    @Override
+    public OwnerSession onAcceptAction(Transaction txn, OwnerSession session, long timestamp) throws DbException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onDeclineAction(Transaction txn, OwnerSession session, long timestamp) throws DbException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onRequestMessage(Transaction txn, OwnerSession session, RequestMessage m) throws DbException, FormatException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onAcceptMessage(Transaction txn, OwnerSession session, MailboxAcceptMessage m) throws DbException, FormatException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onDeclineMessage(Transaction txn, OwnerSession session, DeclineMessage m) throws DbException, FormatException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onAuthMessage(Transaction txn, OwnerSession session, MailboxAuthMessage m) throws DbException, FormatException {
+        return null;
+    }
+
+    @Override
+    public OwnerSession onAbortMessage(Transaction txn, OwnerSession session, AbortMessage m) throws DbException, FormatException {
+        return null;
+    }
+
+    private OwnerSession onLocalRequest(Transaction txn, OwnerSession s, 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);
+        */
+        // 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());
+    }
+
+    private long getLocalTimestamp(OwnerSession s, PeerSession p) {
+        return getLocalTimestamp(p.getLocalTimestamp(),
+                s.getRequestTimestamp());
+    }
 }
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 b6d4525d4..c68382ac7 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
@@ -12,38 +12,37 @@ import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
 
 @Immutable
 @NotNullByDefault
-class OwnerSession
-		extends Session<OwnerState> {
-
-	private final Introducee mailbox, introducee;
-
-	OwnerSession(SessionId sessionId, OwnerState state,
-			long requestTimestamp, Introducee mailbox,
-			Introducee introducee, long sessionCounter) {
-		super(sessionId, state, requestTimestamp, sessionCounter);
-		this.mailbox = mailbox;
-		this.introducee = introducee;
-	}
-
-	OwnerSession(SessionId sessionId, GroupId groupIdA, Author authorA,
-			GroupId groupIdB, Author authorB, long sessionCounter) {
-		this(sessionId, OwnerState.START, -1,
-				new Introducee(sessionId, groupIdA, authorA),
-				new Introducee(sessionId, groupIdB, authorB), sessionCounter);
-	}
-
-	@Override
-	Role getRole() {
-		return INTRODUCER;
-	}
-
-	Introducee getMailbox() {
-		return mailbox;
-	}
-
-	Introducee getIntroducee() {
-		return introducee;
-	}
+class OwnerSession extends Session<OwnerState> {
+
+    private final Introducee mailbox, introducee;
+
+    OwnerSession(SessionId sessionId, OwnerState state,
+                 long requestTimestamp, Introducee mailbox,
+                 Introducee introducee, long sessionCounter) {
+        super(sessionId, state, requestTimestamp, sessionCounter);
+        this.mailbox = mailbox;
+        this.introducee = introducee;
+    }
+
+    OwnerSession(SessionId sessionId, GroupId groupIdA, Author authorA,
+                 GroupId groupIdB, Author authorB, long sessionCounter) {
+        this(sessionId, OwnerState.START, -1,
+                new Introducee(sessionId, groupIdA, authorA),
+                new Introducee(sessionId, groupIdB, authorB), sessionCounter);
+    }
+
+    @Override
+    Role getRole() {
+        return INTRODUCER;
+    }
+
+    Introducee getMailbox() {
+        return mailbox;
+    }
+
+    Introducee getIntroducee() {
+        return introducee;
+    }
 
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java
index a8ac3d6ea..ba0531f2e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java
@@ -11,7 +11,7 @@ enum OwnerState implements State {
 
 	START(0),
 	AWAIT_RESPONSE_M(1),
-	M_DECLINE(2),
+	M_DECLINED(2),
 	AWAIT_RESPONSE_B(3),
 	B_DECLINED(4),
 	AWAIT_AUTH_M(5),
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
index 9845d4a68..85db0aa2b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
@@ -5,33 +5,30 @@ import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
-import javax.annotation.Nullable;
-
 @NotNullByDefault
 interface ProtocolEngine<S extends Session> {
 
-	S onRequestAction(Transaction txn, S session, @Nullable String message,
-			long timestamp) throws DbException;
+    S onRequestAction(Transaction txn, S session, long timestamp) throws DbException;
 
-	S onAcceptAction(Transaction txn, S session, long timestamp)
-			throws DbException;
+    S onAcceptAction(Transaction txn, S session, long timestamp)
+            throws DbException;
 
-	S onDeclineAction(Transaction txn, S session, long timestamp)
-			throws DbException;
+    S onDeclineAction(Transaction txn, S session, long timestamp)
+            throws DbException;
 
-	S onRequestMessage(Transaction txn, S session, RequestMessage m)
-			throws DbException, FormatException;
+    S onRequestMessage(Transaction txn, S session, RequestMessage m)
+            throws DbException, FormatException;
 
-	S onAcceptMessage(Transaction txn, S session, MailboxAcceptMessage m)
-			throws DbException, FormatException;
+    S onAcceptMessage(Transaction txn, S session, MailboxAcceptMessage m)
+            throws DbException, FormatException;
 
-	S onDeclineMessage(Transaction txn, S session, DeclineMessage m)
-			throws DbException, FormatException;
+    S onDeclineMessage(Transaction txn, S session, DeclineMessage m)
+            throws DbException, FormatException;
 
-	S onAuthMessage(Transaction txn, S session, MailboxAuthMessage m)
-			throws DbException, FormatException;
+    S onAuthMessage(Transaction txn, S session, MailboxAuthMessage m)
+            throws DbException, FormatException;
 
-	S onAbortMessage(Transaction txn, S session, AbortMessage m)
-			throws DbException, FormatException;
+    S onAbortMessage(Transaction txn, S session, AbortMessage m)
+            throws DbException, FormatException;
 
 }
-- 
GitLab