From 672a52b2e5014a4fc76adcb8035d9effbb316536 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 17 Apr 2018 18:13:35 -0300
Subject: [PATCH] Implement MessageEncoder and MessageParser

---
 .../briar/introduction2/AcceptMessage.java    |   7 +
 .../introduction2/IntroductionConstants.java  |  14 ++
 .../introduction2/IntroductionValidator.java  |  11 +-
 .../briar/introduction2/MessageEncoder.java   |  52 +++-
 .../introduction2/MessageEncoderImpl.java     | 205 ++++++++++++++++
 .../briar/introduction2/MessageMetadata.java  |  65 +++++
 .../briar/introduction2/MessageParser.java    |  37 +++
 .../introduction2/MessageParserImpl.java      | 144 +++++++++++
 .../IntroductionValidatorTest.java            |  22 +-
 .../MessageEncoderParserIntegrationTest.java  | 231 ++++++++++++++++++
 .../introduction2/MessageEncoderTest.java     |  63 +++++
 .../test/BriarIntegrationTestComponent.java   |   3 +
 .../briar/test/BriarTestUtils.java            |  10 +
 13 files changed, 844 insertions(+), 20 deletions(-)
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java
 create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java
 create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java

diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java
index b9bf92b6c4..77d09c74ac 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java
@@ -18,16 +18,19 @@ class AcceptMessage extends IntroductionMessage {
 
 	private final SessionId sessionId;
 	private final byte[] ephemeralPublicKey;
+	private final long acceptTimestamp;
 	private final Map<TransportId, TransportProperties> transportProperties;
 
 	protected AcceptMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
 			SessionId sessionId,
 			byte[] ephemeralPublicKey,
+			long acceptTimestamp,
 			Map<TransportId, TransportProperties> transportProperties) {
 		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
 		this.ephemeralPublicKey = ephemeralPublicKey;
+		this.acceptTimestamp = acceptTimestamp;
 		this.transportProperties = transportProperties;
 	}
 
@@ -39,6 +42,10 @@ class AcceptMessage extends IntroductionMessage {
 		return ephemeralPublicKey;
 	}
 
+	public long getAcceptTimestamp() {
+		return acceptTimestamp;
+	}
+
 	public Map<TransportId, TransportProperties> getTransportProperties() {
 		return transportProperties;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java
new file mode 100644
index 0000000000..4098b2421e
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java
@@ -0,0 +1,14 @@
+package org.briarproject.briar.introduction2;
+
+interface IntroductionConstants {
+
+	// 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";
+	String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted";
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java
index ce8359701f..1bdf07ff64 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java
@@ -28,7 +28,6 @@ import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
 import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
 import static org.briarproject.briar.introduction2.MessageType.AUTH;
-import static org.briarproject.briar.introduction2.MessageType.REQUEST;
 
 
 @Immutable
@@ -79,8 +78,8 @@ class IntroductionValidator extends BdfMessageValidator {
 		checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
 
 		BdfDictionary meta = messageEncoder
-				.encodeRequestMetadata(REQUEST, m.getTimestamp(), false,
-						false, false, false, false);
+				.encodeRequestMetadata(m.getTimestamp(), false, false,
+						false, false);
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
@@ -92,7 +91,7 @@ class IntroductionValidator extends BdfMessageValidator {
 
 	private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
 			throws FormatException {
-		checkSize(body, 5);
+		checkSize(body, 6);
 
 		byte[] sessionIdBytes = body.getRaw(1);
 		checkLength(sessionIdBytes, UniqueId.LENGTH);
@@ -103,7 +102,9 @@ class IntroductionValidator extends BdfMessageValidator {
 		byte[] ephemeralPublicKey = body.getRaw(3);
 		checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
 
-		BdfDictionary transportProperties = body.getDictionary(4);
+		body.getLong(4);
+
+		BdfDictionary transportProperties = body.getDictionary(5);
 		if (transportProperties.size() < 1) throw new FormatException();
 		for (String tId : transportProperties.keySet()) {
 			checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java
index 25371b68cb..390ea9b0fb 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java
@@ -1,15 +1,57 @@
 package org.briarproject.briar.introduction2;
 
 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 {
 
-	BdfDictionary encodeRequestMetadata(MessageType type,
-			long timestamp, boolean local, boolean read, boolean visible,
-			boolean available, boolean accepted);
+	BdfDictionary encodeRequestMetadata(long timestamp, boolean local,
+			boolean read, boolean available, boolean accepted);
+
+	BdfDictionary encodeMetadata(MessageType type,
+			@Nullable SessionId sessionId, long timestamp, boolean local,
+			boolean read, boolean visible);
+
+	void addSessionId(BdfDictionary meta, SessionId sessionId);
+
+	void setVisibleInUi(BdfDictionary meta, boolean visible);
+
+	void setAvailableToAnswer(BdfDictionary meta, boolean available);
+
+	void setInvitationAccepted(BdfDictionary meta, boolean accepted);
+
+	Message encodeRequestMessage(GroupId contactGroupId, long timestamp,
+			@Nullable MessageId previousMessageId, Author author,
+			@Nullable String message);
+
+	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 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);
 
-	BdfDictionary encodeMetadata(MessageType type, SessionId sessionId,
-			long timestamp, boolean local, boolean read, boolean visible);
+	Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
+			@Nullable MessageId previousMessageId, SessionId sessionId);
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java
new file mode 100644
index 0000000000..7f44671e0c
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java
@@ -0,0 +1,205 @@
+package org.briarproject.briar.introduction2;
+
+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.client.MessageTrackerConstants.MSG_KEY_READ;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
+import static org.briarproject.briar.introduction2.MessageType.ABORT;
+import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
+import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
+import static org.briarproject.briar.introduction2.MessageType.AUTH;
+import static org.briarproject.briar.introduction2.MessageType.DECLINE;
+import static org.briarproject.briar.introduction2.MessageType.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,
+			boolean local, boolean read, boolean available,
+			boolean accepted) {
+		BdfDictionary meta =
+				encodeMetadata(REQUEST, null, timestamp, local, read, false);
+		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
+		meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted);
+		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 void setInvitationAccepted(BdfDictionary meta, boolean accepted) {
+		meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted);
+	}
+
+	@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
+		);
+		try {
+			return messageFactory.createMessage(contactGroupId, timestamp,
+					clientHelper.toByteArray(body));
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+
+	@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,
+				encodeTransportProperties(transportProperties)
+		);
+		try {
+			return messageFactory.createMessage(contactGroupId, timestamp,
+					clientHelper.toByteArray(body));
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+
+	@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
+		);
+		try {
+			return messageFactory.createMessage(contactGroupId, timestamp,
+					clientHelper.toByteArray(body));
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+
+	@Override
+	public Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
+			@Nullable MessageId previousMessageId, SessionId sessionId) {
+		return encodeMessage(ACTIVATE, contactGroupId, sessionId, timestamp,
+				previousMessageId);
+	}
+
+	@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
+		);
+		try {
+			return messageFactory.createMessage(contactGroupId, timestamp,
+					clientHelper.toByteArray(body));
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+
+	private BdfDictionary encodeTransportProperties(
+			Map<TransportId, TransportProperties> map) {
+		BdfDictionary d = new BdfDictionary();
+		for (Map.Entry<TransportId, TransportProperties> e : map.entrySet()) {
+			d.put(e.getKey().getString(), e.getValue());
+		}
+		return d;
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java
new file mode 100644
index 0000000000..67ba5b0319
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java
@@ -0,0 +1,65 @@
+package org.briarproject.briar.introduction2;
+
+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;
+	private final boolean local, read, visible, available, accepted;
+
+	MessageMetadata(MessageType type, @Nullable SessionId sessionId,
+			long timestamp, boolean local, boolean read, boolean visible,
+			boolean available, boolean accepted) {
+		this.type = type;
+		this.sessionId = sessionId;
+		this.timestamp = timestamp;
+		this.local = local;
+		this.read = read;
+		this.visible = visible;
+		this.available = available;
+		this.accepted = accepted;
+	}
+
+	MessageType getMessageType() {
+		return type;
+	}
+
+	@Nullable
+	public SessionId getSessionId() {
+		return sessionId;
+	}
+
+	long getTimestamp() {
+		return timestamp;
+	}
+
+	boolean isLocal() {
+		return local;
+	}
+
+	boolean isRead() {
+		return read;
+	}
+
+	boolean isVisibleInConversation() {
+		return visible;
+	}
+
+	boolean isAvailableToAnswer() {
+		return available;
+	}
+
+	public boolean wasAccepted() {
+		return accepted;
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java
new file mode 100644
index 0000000000..45526a0919
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java
@@ -0,0 +1,37 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.briar.api.client.SessionId;
+
+@NotNullByDefault
+interface MessageParser {
+
+	BdfDictionary getMessagesVisibleInUiQuery();
+
+	BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId);
+
+	MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException;
+
+	RequestMessage parseRequestMessage(Message m, BdfList body)
+			throws FormatException;
+
+	AcceptMessage parseAcceptMessage(Message m, BdfList body)
+			throws FormatException;
+
+	DeclineMessage parseDeclineMessage(Message m, BdfList body)
+			throws FormatException;
+
+	AuthMessage parseAuthMessage(Message m, BdfList body)
+			throws FormatException;
+
+	ActivateMessage parseActivateMessage(Message m, BdfList body)
+			throws FormatException;
+
+	AbortMessage parseAbortMessage(Message m, BdfList body)
+			throws FormatException;
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java
new file mode 100644
index 0000000000..1c55be11d4
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java
@@ -0,0 +1,144 @@
+package org.briarproject.briar.introduction2;
+
+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.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.client.MessageTrackerConstants.MSG_KEY_READ;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
+import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
+import static org.briarproject.briar.introduction2.MessageType.REQUEST;
+
+@NotNullByDefault
+class MessageParserImpl implements MessageParser {
+
+	private final ClientHelper clientHelper;
+
+	@Inject
+	MessageParserImpl(ClientHelper clientHelper) {
+		this.clientHelper = clientHelper;
+	}
+
+	@Override
+	public BdfDictionary getMessagesVisibleInUiQuery() {
+		return BdfDictionary.of(new BdfEntry(MSG_KEY_VISIBLE_IN_UI, true));
+	}
+
+	@Override
+	public BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId) {
+		return BdfDictionary.of(
+				new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true),
+				new BdfEntry(MSG_KEY_MESSAGE_TYPE, REQUEST.getValue()),
+				new BdfEntry(MSG_KEY_SESSION_ID, sessionId)
+		);
+	}
+
+	@Override
+	public MessageMetadata parseMetadata(BdfDictionary d)
+			throws FormatException {
+		MessageType type = MessageType
+				.fromValue(d.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
+		byte[] sessionIdBytes = d.getOptionalRaw(MSG_KEY_SESSION_ID);
+		SessionId sessionId =
+				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);
+		boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false);
+		boolean accepted = d.getBoolean(MSG_KEY_INVITATION_ACCEPTED, false);
+		return new MessageMetadata(type, sessionId, timestamp, local, read,
+				visible, available, accepted);
+	}
+
+	@Override
+	public RequestMessage parseRequestMessage(Message m, BdfList body)
+			throws FormatException {
+		byte[] previousMsgBytes = body.getOptionalRaw(1);
+		MessageId previousMessageId = (previousMsgBytes == null ? null :
+				new MessageId(previousMsgBytes));
+		Author author = clientHelper.parseAndValidateAuthor(body.getList(2));
+		String message = body.getOptionalString(3);
+		return new RequestMessage(m.getId(), m.getGroupId(),
+				m.getTimestamp(), previousMessageId, author, message);
+	}
+
+	@Override
+	public AcceptMessage parseAcceptMessage(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));
+		byte[] ephemeralPublicKey = body.getRaw(3);
+		long acceptTimestamp = body.getLong(4);
+		Map<TransportId, TransportProperties> transportProperties = clientHelper
+				.parseAndValidateTransportPropertiesMap(body.getDictionary(5));
+		return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
+				previousMessageId, sessionId, ephemeralPublicKey,
+				acceptTimestamp, transportProperties);
+	}
+
+	@Override
+	public DeclineMessage parseDeclineMessage(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 DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
+				previousMessageId, sessionId);
+	}
+
+	@Override
+	public AuthMessage parseAuthMessage(Message m, BdfList body)
+			throws FormatException {
+		SessionId sessionId = new SessionId(body.getRaw(1));
+		byte[] previousMsgBytes = body.getRaw(2);
+		MessageId previousMessageId = new MessageId(previousMsgBytes);
+		byte[] mac = body.getRaw(3);
+		byte[] signature = body.getRaw(4);
+		return new AuthMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
+				previousMessageId, sessionId, mac, signature);
+	}
+
+	@Override
+	public ActivateMessage parseActivateMessage(Message m, BdfList body)
+			throws FormatException {
+		SessionId sessionId = new SessionId(body.getRaw(1));
+		byte[] previousMsgBytes = body.getRaw(2);
+		MessageId previousMessageId = new MessageId(previousMsgBytes);
+		return new ActivateMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
+				previousMessageId, sessionId);
+	}
+
+	@Override
+	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);
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java
index fc01094979..0fa094a5ea 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java
@@ -41,6 +41,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	private final String text =
 			getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
 	private final BdfDictionary meta = new BdfDictionary();
+	private final long acceptTimestamp = 42;
 	private final BdfDictionary transportProperties = BdfDictionary.of(
 			new BdfEntry("transportId",  new BdfDictionary())
 	);
@@ -124,7 +125,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	public void testAcceptsAccept() throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
 				previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				transportProperties);
+				acceptTimestamp, transportProperties);
 		context.checking(new Expectations() {{
 			oneOf(clientHelper).parseAndValidateTransportProperties(
 					transportProperties.getDictionary("transportId"));
@@ -140,7 +141,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	public void testRejectsTooShortBodyForAccept() throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
 				previousMsgId.getBytes(),
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp);
 		validator.validateMessage(message, group, body);
 	}
 
@@ -148,7 +149,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	public void testRejectsTooLongBodyForAccept() throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
 				previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				transportProperties, null);
+				acceptTimestamp, transportProperties, null);
 		validator.validateMessage(message, group, body);
 	}
 
@@ -156,7 +157,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	public void testRejectsInvalidSessionIdForAccept() throws Exception {
 		BdfList body =
 				BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(),
-						getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
+						getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
 						transportProperties);
 		validator.validateMessage(message, group, body);
 	}
@@ -164,7 +165,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	@Test(expected = FormatException.class)
 	public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
-				null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
+				null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
 				transportProperties);
 		validator.validateMessage(message, group, body);
 	}
@@ -173,7 +174,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	public void testRejectsTooLongPublicKeyForAccept() throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
 				previousMsgId.getBytes(),
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), transportProperties);
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
+				transportProperties);
 		validator.validateMessage(message, group, body);
 	}
 
@@ -182,7 +184,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 			throws Exception {
 		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
 				previousMsgId.getBytes(),
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), new BdfDictionary());
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
+				new BdfDictionary());
 		validator.validateMessage(message, group, body);
 	}
 
@@ -397,9 +400,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	private void expectEncodeRequestMetadata() {
 		context.checking(new Expectations() {{
 			oneOf(messageEncoder)
-					.encodeRequestMetadata(REQUEST, message.getTimestamp(),
-							false, false,
-							false, false, false);
+					.encodeRequestMetadata(message.getTimestamp(), false, false,
+							false, false);
 			will(returnValue(meta));
 		}});
 	}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java
new file mode 100644
index 0000000000..e623f814ac
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java
@@ -0,0 +1,231 @@
+package org.briarproject.briar.introduction2;
+
+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.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
+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.bramble.test.BrambleTestCase;
+import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.test.BriarIntegrationTestComponent;
+import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
+import org.junit.Test;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
+import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
+import static org.briarproject.briar.introduction2.MessageType.ABORT;
+import static org.briarproject.briar.introduction2.MessageType.REQUEST;
+import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
+
+	@Inject
+	ClientHelper clientHelper;
+	@Inject
+	MessageFactory messageFactory;
+	@Inject
+	AuthorFactory authorFactory;
+
+	private final MessageEncoder messageEncoder;
+	private final MessageParser messageParser;
+
+	private final GroupId groupId = new GroupId(getRandomId());
+	private final long timestamp = 42L;
+	private final SessionId sessionId = new SessionId(getRandomId());
+	private final MessageId previousMsgId = new MessageId(getRandomId());
+	private final Author author;
+	private final String text =
+			getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
+	private final byte[] ephemeralPublicKey =
+			getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+	private final byte[] mac = getRandomBytes(MAC_BYTES);
+	private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
+
+	public MessageEncoderParserIntegrationTest() {
+		BriarIntegrationTestComponent component =
+				DaggerBriarIntegrationTestComponent.builder().build();
+		component.inject(this);
+
+		messageEncoder = new MessageEncoderImpl(clientHelper, messageFactory);
+		messageParser = new MessageParserImpl(clientHelper);
+		author = getRealAuthor(authorFactory);
+	}
+
+	@Test
+	public void testRequestMessageMetadata() throws FormatException {
+		BdfDictionary d = messageEncoder
+				.encodeRequestMetadata(timestamp, true, false, false,
+						true);
+		MessageMetadata meta = messageParser.parseMetadata(d);
+
+		assertEquals(REQUEST, meta.getMessageType());
+		assertNull(meta.getSessionId());
+		assertEquals(timestamp, meta.getTimestamp());
+		assertTrue(meta.isLocal());
+		assertFalse(meta.isRead());
+		assertFalse(meta.isVisibleInConversation());
+		assertFalse(meta.isAvailableToAnswer());
+		assertTrue(meta.wasAccepted());
+	}
+
+	@Test
+	public void testMessageMetadata() throws FormatException {
+		BdfDictionary d = messageEncoder
+				.encodeMetadata(ABORT, sessionId, timestamp, false, true,
+						false);
+		MessageMetadata meta = messageParser.parseMetadata(d);
+
+		assertEquals(ABORT, meta.getMessageType());
+		assertEquals(sessionId, meta.getSessionId());
+		assertEquals(timestamp, meta.getTimestamp());
+		assertFalse(meta.isLocal());
+		assertTrue(meta.isRead());
+		assertFalse(meta.isVisibleInConversation());
+		assertFalse(meta.isAvailableToAnswer());
+		assertFalse(meta.wasAccepted());
+	}
+
+	@Test
+	public void testRequestMessage() throws FormatException {
+		Message m = messageEncoder
+				.encodeRequestMessage(groupId, timestamp, previousMsgId, author,
+						text);
+		RequestMessage rm =
+				messageParser.parseRequestMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(author, rm.getAuthor());
+		assertEquals(text, rm.getMessage());
+	}
+
+	@Test
+	public void testRequestMessageWithPreviousMsgNull() throws FormatException {
+		Message m = messageEncoder
+				.encodeRequestMessage(groupId, timestamp, null, author, text);
+		RequestMessage rm =
+				messageParser.parseRequestMessage(m, clientHelper.toList(m));
+
+		assertNull(rm.getPreviousMessageId());
+	}
+
+	@Test
+	public void testRequestMessageWithMsgNull() throws FormatException {
+		Message m = messageEncoder
+				.encodeRequestMessage(groupId, timestamp, previousMsgId, author,
+						null);
+		RequestMessage rm =
+				messageParser.parseRequestMessage(m, clientHelper.toList(m));
+
+		assertNull(rm.getMessage());
+	}
+
+	@Test
+	public void testAcceptMessage() throws Exception {
+		Map<TransportId, TransportProperties> transportProperties =
+				getTransportPropertiesMap(2);
+
+		long acceptTimestamp = 1337L;
+		Message m = messageEncoder
+				.encodeAcceptMessage(groupId, timestamp, previousMsgId,
+						sessionId, ephemeralPublicKey, acceptTimestamp,
+						transportProperties);
+		AcceptMessage rm =
+				messageParser.parseAcceptMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(sessionId, rm.getSessionId());
+		assertArrayEquals(ephemeralPublicKey, rm.getEphemeralPublicKey());
+		assertEquals(acceptTimestamp, rm.getAcceptTimestamp());
+		assertEquals(transportProperties, rm.getTransportProperties());
+	}
+
+	@Test
+	public void testDeclineMessage() throws Exception {
+		Message m = messageEncoder
+				.encodeDeclineMessage(groupId, timestamp, previousMsgId,
+						sessionId);
+		DeclineMessage rm =
+				messageParser.parseDeclineMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(sessionId, rm.getSessionId());
+	}
+
+	@Test
+	public void testAuthMessage() throws Exception {
+		Message m = messageEncoder
+				.encodeAuthMessage(groupId, timestamp, previousMsgId,
+						sessionId, mac, signature);
+		AuthMessage rm =
+				messageParser.parseAuthMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(sessionId, rm.getSessionId());
+		assertArrayEquals(mac, rm.getMac());
+		assertArrayEquals(signature, rm.getSignature());
+	}
+
+	@Test
+	public void testActivateMessage() throws Exception {
+		Message m = messageEncoder
+				.encodeActivateMessage(groupId, timestamp, previousMsgId,
+						sessionId);
+		ActivateMessage rm =
+				messageParser.parseActivateMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(sessionId, rm.getSessionId());
+	}
+
+	@Test
+	public void testAbortMessage() throws Exception {
+		Message m = messageEncoder
+				.encodeAbortMessage(groupId, timestamp, previousMsgId,
+						sessionId);
+		AbortMessage rm =
+				messageParser.parseAbortMessage(m, clientHelper.toList(m));
+
+		assertEquals(m.getId(), rm.getMessageId());
+		assertEquals(m.getGroupId(), rm.getGroupId());
+		assertEquals(m.getTimestamp(), rm.getTimestamp());
+		assertEquals(previousMsgId, rm.getPreviousMessageId());
+		assertEquals(sessionId, rm.getSessionId());
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java
new file mode 100644
index 0000000000..c7d60662d4
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java
@@ -0,0 +1,63 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
+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.bramble.test.BrambleMockTestCase;
+import org.jmock.Expectations;
+import org.junit.Test;
+
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
+import static org.briarproject.briar.introduction2.MessageType.REQUEST;
+
+public class MessageEncoderTest extends BrambleMockTestCase {
+
+	private final ClientHelper clientHelper = context.mock(ClientHelper.class);
+	private final MessageFactory messageFactory =
+			context.mock(MessageFactory.class);
+	private final MessageEncoder messageEncoder =
+			new MessageEncoderImpl(clientHelper, messageFactory);
+
+	private final GroupId groupId = new GroupId(getRandomId());
+	private final long timestamp = 42L;
+	private final Message message =
+			new Message(new MessageId(getRandomId()), groupId, timestamp,
+					getRandomBytes(48));
+	private final byte[] body = getRandomBytes(42);
+	private final Author author = getAuthor();
+	private final BdfList authorList = new BdfList();
+	private final String text =
+			getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
+
+	@Test
+	public void testEncodeRequestMessage() throws FormatException {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).toList(author);
+			will(returnValue(authorList));
+		}});
+		expectCreateMessage(
+				BdfList.of(REQUEST.getValue(), null, authorList, text));
+
+		messageEncoder
+				.encodeRequestMessage(groupId, timestamp, null, author, text);
+	}
+
+	private void expectCreateMessage(BdfList bodyList) throws FormatException {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).toByteArray(bodyList);
+			will(returnValue(body));
+			oneOf(messageFactory).createMessage(groupId, timestamp, body);
+			will(returnValue(message));
+		}});
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
index 1b2344d3ad..1afcdea7bc 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
@@ -37,6 +37,7 @@ import org.briarproject.briar.blog.BlogModule;
 import org.briarproject.briar.client.BriarClientModule;
 import org.briarproject.briar.forum.ForumModule;
 import org.briarproject.briar.introduction.IntroductionModule;
+import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest;
 import org.briarproject.briar.messaging.MessagingModule;
 import org.briarproject.briar.privategroup.PrivateGroupModule;
 import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
@@ -76,6 +77,8 @@ public interface BriarIntegrationTestComponent {
 
 	void inject(BriarIntegrationTest<BriarIntegrationTestComponent> init);
 
+	void inject(MessageEncoderParserIntegrationTest init);
+
 	void inject(BlogModule.EagerSingletons init);
 
 	void inject(ContactModule.EagerSingletons init);
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
index d29fc0b541..5de39e9f56 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
@@ -1,10 +1,15 @@
 package org.briarproject.briar.test;
 
 import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.MessageTracker.GroupCount;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 
 public class BriarTestUtils {
@@ -25,4 +30,9 @@ public class BriarTestUtils {
 		assertEquals(unreadCount, c1.getUnreadCount());
 	}
 
+	public static Author getRealAuthor(AuthorFactory authorFactory) {
+		return authorFactory.createAuthor(getRandomString(5),
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
+	}
+
 }
-- 
GitLab