diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java b/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..79a43ad6649010bdea912efbe12026f525f43203
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java
@@ -0,0 +1,166 @@
+package org.briarproject.api.forum;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfEntry;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.sync.GroupId;
+
+import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
+import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
+import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
+import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
+import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.forum.ForumConstants.TYPE;
+
+public interface ForumSharingMessage {
+
+	abstract class BaseMessage {
+		private final GroupId groupId;
+		private final SessionId sessionId;
+
+		public BaseMessage(GroupId groupId, SessionId sessionId) {
+
+			this.groupId = groupId;
+			this.sessionId = sessionId;
+		}
+
+		public BdfList toBdfList() {
+			return BdfList.of(getType(), getSessionId());
+		}
+
+		public abstract BdfDictionary toBdfDictionary();
+
+		protected BdfDictionary toBdfDictionaryHelper() {
+			return BdfDictionary.of(
+					new BdfEntry(TYPE, getType()),
+					new BdfEntry(GROUP_ID, groupId),
+					new BdfEntry(SESSION_ID, sessionId)
+			);
+		}
+
+		public static BaseMessage from(GroupId groupId, BdfDictionary d)
+				throws FormatException {
+
+			long type = d.getLong(TYPE);
+
+			if (type == SHARE_MSG_TYPE_INVITATION)
+				return Invitation.from(groupId, d);
+			else
+				return SimpleMessage.from(type, groupId, d);
+		}
+
+		public abstract long getType();
+
+		public GroupId getGroupId() {
+			return groupId;
+		}
+
+		public SessionId getSessionId() {
+			return sessionId;
+		}
+	}
+
+	class Invitation extends BaseMessage {
+
+		private final String forumName;
+		private final byte[] forumSalt;
+		private final String message;
+
+		public Invitation(GroupId groupId, SessionId sessionId,
+				String forumName, byte[] forumSalt, String message) {
+
+			super(groupId, sessionId);
+
+			this.forumName = forumName;
+			this.forumSalt = forumSalt;
+			this.message = message;
+		}
+
+		@Override
+		public long getType() {
+			return SHARE_MSG_TYPE_INVITATION;
+		}
+
+		@Override
+		public BdfList toBdfList() {
+			BdfList list = super.toBdfList();
+			list.add(forumName);
+			list.add(forumSalt);
+			if (message != null) list.add(message);
+			return list;
+		}
+
+		@Override
+		public BdfDictionary toBdfDictionary() {
+			BdfDictionary d = toBdfDictionaryHelper();
+			d.put(FORUM_NAME, forumName);
+			d.put(FORUM_SALT, forumSalt);
+			if (message != null) d.put(INVITATION_MSG, message);
+			return d;
+		}
+
+		public static Invitation from(GroupId groupId, BdfDictionary d)
+				throws FormatException {
+
+			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
+			String forumName = d.getString(FORUM_NAME);
+			byte[] forumSalt = d.getRaw(FORUM_SALT);
+			String message = d.getOptionalString(INVITATION_MSG);
+
+			return new Invitation(groupId, sessionId, forumName, forumSalt,
+					message);
+		}
+
+		public String getForumName() {
+			return forumName;
+		}
+
+		public byte[] getForumSalt() {
+			return forumSalt;
+		}
+
+		public String getMessage() {
+			return message;
+		}
+	}
+
+	class SimpleMessage extends BaseMessage {
+
+		private final long type;
+
+		public SimpleMessage(long type, GroupId groupId, SessionId sessionId) {
+			super(groupId, sessionId);
+			this.type = type;
+		}
+
+		@Override
+		public long getType() {
+			return type;
+		}
+
+		@Override
+		public BdfDictionary toBdfDictionary() {
+			return toBdfDictionaryHelper();
+		}
+
+		public static SimpleMessage from(long type, GroupId groupId,
+				BdfDictionary d) throws FormatException {
+
+			if (type != SHARE_MSG_TYPE_ACCEPT &&
+					type != SHARE_MSG_TYPE_DECLINE &&
+					type != SHARE_MSG_TYPE_LEAVE &&
+					type != SHARE_MSG_TYPE_ABORT) throw new FormatException();
+
+			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
+			return new SimpleMessage(type, groupId, sessionId);
+		}
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
index 1031f3c10d29368749f9ec0f8b35802004301bd7..cbeb56d60f2ecf2c14000f897ef145c849bea0c8 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
@@ -55,11 +55,7 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.clients.ProtocolEngine.StateUpdate;
 import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
-import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
-import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
 import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
 import static org.briarproject.api.forum.ForumConstants.LOCAL;
 import static org.briarproject.api.forum.ForumConstants.READ;
@@ -83,6 +79,8 @@ import static org.briarproject.api.forum.ForumConstants.TIME;
 import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US;
 import static org.briarproject.api.forum.ForumConstants.TYPE;
 import static org.briarproject.api.forum.ForumManager.RemoveForumHook;
+import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
+import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
 import static org.briarproject.forum.ForumSharingSessionState.fromBdfDictionary;
 import static org.briarproject.forum.SharerSessionState.Action;
 
@@ -178,11 +176,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 	@Override
 	protected void incomingMessage(Transaction txn, Message m, BdfList body,
-			BdfDictionary msg) throws DbException, FormatException {
+			BdfDictionary d) throws DbException, FormatException {
 
-		SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
-		long type = msg.getLong(TYPE);
-		if (type == SHARE_MSG_TYPE_INVITATION) {
+		BaseMessage msg = BaseMessage.from(m.getGroupId(), d);
+		SessionId sessionId = msg.getSessionId();
+
+		if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
 			// we are an invitee who just received a new invitation
 			boolean stateExists = true;
 			try {
@@ -197,8 +196,9 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				if (stateExists) throw new FormatException();
 
 				// check if forum can be shared
-				Forum f = forumFactory.createForum(msg.getString(FORUM_NAME),
-						msg.getRaw(FORUM_SALT));
+				Invitation invitation = (Invitation) msg;
+				Forum f = forumFactory.createForum(invitation.getForumName(),
+						invitation.getForumSalt());
 				ContactId contactId = getContactId(txn, m.getGroupId());
 				Contact contact = db.getContact(txn, contactId);
 				if (!canBeShared(txn, f.getId(), contact))
@@ -206,7 +206,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 				// initialize state and process invitation
 				InviteeSessionState state =
-						initializeInviteeState(txn, contactId, msg);
+						initializeInviteeState(txn, contactId, invitation);
 				InviteeEngine engine = new InviteeEngine(forumFactory);
 				processInviteeStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
@@ -214,15 +214,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				deleteMessage(txn, m.getId());
 			}
-		} else if (type == SHARE_MSG_TYPE_ACCEPT ||
-				type == SHARE_MSG_TYPE_DECLINE) {
+		} else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
+				msg.getType() == SHARE_MSG_TYPE_DECLINE) {
 			// we are a sharer who just received a response
 			SharerSessionState state = getSessionStateForSharer(txn, sessionId);
 			SharerEngine engine = new SharerEngine();
 			processSharerStateUpdate(txn, m.getId(),
 					engine.onMessageReceived(state, msg));
-		} else if (type == SHARE_MSG_TYPE_LEAVE ||
-				type == SHARE_MSG_TYPE_ABORT) {
+		} else if (msg.getType() == SHARE_MSG_TYPE_LEAVE ||
+				msg.getType() == SHARE_MSG_TYPE_ABORT) {
 			// we don't know who we are, so figure it out
 			ForumSharingSessionState s = getSessionState(txn, sessionId, true);
 			if (s instanceof SharerSessionState) {
@@ -322,34 +322,33 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 			Map<MessageId, BdfDictionary> map = clientHelper
 					.getMessageMetadataAsDictionary(txn, group.getId());
 			for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
-				BdfDictionary msg = m.getValue();
+				BdfDictionary d = m.getValue();
 				try {
-					if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION)
+					if (d.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION)
 						continue;
 
+					Invitation msg = Invitation.from(group.getId(), d);
 					MessageStatus status =
 							db.getMessageStatus(txn, contactId, m.getKey());
-					SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
-					String name = msg.getString(FORUM_NAME);
-					String message = msg.getOptionalString(INVITATION_MSG);
-					long time = msg.getLong(TIME);
-					boolean local = msg.getBoolean(LOCAL);
-					boolean read = msg.getBoolean(READ, false);
+					long time = d.getLong(TIME);
+					boolean local = d.getBoolean(LOCAL);
+					boolean read = d.getBoolean(READ, false);
 					boolean available = false;
 					if (!local) {
 						// figure out whether the forum is still available
 						ForumSharingSessionState s =
-								getSessionState(txn, sessionId, true);
+								getSessionState(txn, msg.getSessionId(), true);
 						if (!(s instanceof InviteeSessionState))
 							continue;
 						available = ((InviteeSessionState) s).getState() ==
 								InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
 					}
 					ForumInvitationMessage im =
-							new ForumInvitationMessage(m.getKey(), sessionId,
-									contactId, name, message, available, time,
-									local, status.isSent(), status.isSeen(),
-									read);
+							new ForumInvitationMessage(m.getKey(),
+									msg.getSessionId(), contactId,
+									msg.getForumName(), msg.getMessage(),
+									available, time, local, status.isSent(),
+									status.isSeen(), read);
 					list.add(im);
 				} catch (FormatException e) {
 					if (LOG.isLoggable(WARNING))
@@ -508,13 +507,13 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private InviteeSessionState initializeInviteeState(Transaction txn,
-			ContactId contactId, BdfDictionary msg)
+			ContactId contactId, Invitation msg)
 			throws FormatException, DbException {
 
 		Contact c = db.getContact(txn, contactId);
 		Group group = getContactGroup(c);
-		String name = msg.getString(FORUM_NAME);
-		byte[] salt = msg.getRaw(FORUM_SALT);
+		String name = msg.getForumName();
+		byte[] salt = msg.getForumSalt();
 		Forum f = forumFactory.createForum(name, salt);
 
 		// create local message to keep engine state
@@ -524,11 +523,10 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		Message m = clientHelper.createMessage(localGroup.getId(), now,
 				BdfList.of(mSalt));
 
-		SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
-
-		InviteeSessionState s = new InviteeSessionState(sessionId, m.getId(),
-				group.getId(), InviteeSessionState.State.AWAIT_INVITATION,
-				contactId, f.getId(), f.getName(), f.getSalt());
+		InviteeSessionState s = new InviteeSessionState(msg.getSessionId(),
+				m.getId(), group.getId(),
+				InviteeSessionState.State.AWAIT_INVITATION, contactId,
+				f.getId(), f.getName(), f.getSalt());
 
 		// save local state to database
 		BdfDictionary d = s.toBdfDictionary();
@@ -646,7 +644,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<ForumSharingSessionState, BdfDictionary> result)
+			StateUpdate<ForumSharingSessionState, BaseMessage> result)
 			throws DbException, FormatException {
 
 		// perform actions based on new local state
@@ -658,8 +656,8 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				result.localState.toBdfDictionary());
 
 		// send messages
-		for (BdfDictionary d : result.toSend) {
-			sendMessage(txn, d);
+		for (BaseMessage msg : result.toSend) {
+			sendMessage(txn, msg);
 		}
 
 		// broadcast events
@@ -678,11 +676,11 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processSharerStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<SharerSessionState, BdfDictionary> result)
+			StateUpdate<SharerSessionState, BaseMessage> result)
 			throws DbException, FormatException {
 
-		StateUpdate<ForumSharingSessionState, BdfDictionary> r =
-				new StateUpdate<ForumSharingSessionState, BdfDictionary>(
+		StateUpdate<ForumSharingSessionState, BaseMessage> r =
+				new StateUpdate<ForumSharingSessionState, BaseMessage>(
 						result.deleteMessage, result.deleteState,
 						result.localState, result.toSend, result.toBroadcast);
 
@@ -690,11 +688,11 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<InviteeSessionState, BdfDictionary> result)
+			StateUpdate<InviteeSessionState, BaseMessage> result)
 			throws DbException, FormatException {
 
-		StateUpdate<ForumSharingSessionState, BdfDictionary> r =
-				new StateUpdate<ForumSharingSessionState, BdfDictionary>(
+		StateUpdate<ForumSharingSessionState, BaseMessage> r =
+				new StateUpdate<ForumSharingSessionState, BaseMessage>(
 						result.deleteMessage, result.deleteState,
 						result.localState, result.toSend, result.toBroadcast);
 
@@ -751,50 +749,23 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private void sendMessage(Transaction txn, BdfDictionary m)
+	private void sendMessage(Transaction txn, BaseMessage m)
 			throws FormatException, DbException {
 
-		BdfList list = encodeMessage(m);
-		byte[] body = clientHelper.toByteArray(list);
-		GroupId groupId = new GroupId(m.getRaw(GROUP_ID));
-		Group group = db.getGroup(txn, groupId);
+		byte[] body = clientHelper.toByteArray(m.toBdfList());
+		Group group = db.getGroup(txn, m.getGroupId());
 		long timestamp = clock.currentTimeMillis();
 
 		// add message itself as metadata
-		m.put(LOCAL, true);
-		m.put(TIME, timestamp);
-		Metadata meta = metadataEncoder.encode(m);
+		BdfDictionary d = m.toBdfDictionary();
+		d.put(LOCAL, true);
+		d.put(TIME, timestamp);
+		Metadata meta = metadataEncoder.encode(d);
 
 		messageQueueManager
 				.sendMessage(txn, group, timestamp, body, meta);
 	}
 
-	private BdfList encodeMessage(BdfDictionary m) throws FormatException {
-		long type = m.getLong(TYPE);
-
-		BdfList list;
-		if (type == SHARE_MSG_TYPE_INVITATION) {
-			list = BdfList.of(type,
-					m.getRaw(SESSION_ID),
-					m.getString(FORUM_NAME),
-					m.getRaw(FORUM_SALT)
-			);
-			String msg = m.getOptionalString(INVITATION_MSG);
-			if (msg != null) list.add(msg);
-		} else if (type == SHARE_MSG_TYPE_ACCEPT) {
-			list = BdfList.of(type, m.getRaw(SESSION_ID));
-		} else if (type == SHARE_MSG_TYPE_DECLINE) {
-			list = BdfList.of(type, m.getRaw(SESSION_ID));
-		} else if (type == SHARE_MSG_TYPE_LEAVE) {
-			list = BdfList.of(type, m.getRaw(SESSION_ID));
-		} else if (type == SHARE_MSG_TYPE_ABORT) {
-			list = BdfList.of(type, m.getRaw(SESSION_ID));
-		} else {
-			throw new FormatException();
-		}
-		return list;
-	}
-
 	private Group getContactGroup(Contact c) {
 		return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
 	}
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingValidator.java b/briar-core/src/org/briarproject/forum/ForumSharingValidator.java
index c204623a27dee2dc193befcaaad12ec915a12cba..b162579433ad180719d24bd25b628dc3b32ea9da 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingValidator.java
+++ b/briar-core/src/org/briarproject/forum/ForumSharingValidator.java
@@ -73,7 +73,6 @@ class ForumSharingValidator extends BdfMessageValidator {
 		// Return the metadata
 		d.put(TYPE, type);
 		d.put(SESSION_ID, id);
-		d.put(GROUP_ID, m.getGroupId());
 		d.put(LOCAL, false);
 		d.put(TIME, m.getTimestamp());
 		return d;
diff --git a/briar-core/src/org/briarproject/forum/InviteeEngine.java b/briar-core/src/org/briarproject/forum/InviteeEngine.java
index 3a18581a83064834c22569dff21095d383e0813f..c7c9ff6223c159fb163d6e6504d81c5600cc4246 100644
--- a/briar-core/src/org/briarproject/forum/InviteeEngine.java
+++ b/briar-core/src/org/briarproject/forum/InviteeEngine.java
@@ -3,22 +3,17 @@ package org.briarproject.forum;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ProtocolEngine;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.ForumInvitationReceivedEvent;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumFactory;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
@@ -28,7 +23,8 @@ import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_S
 import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
 import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
 import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TYPE;
+import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
+import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
 import static org.briarproject.forum.InviteeSessionState.Action;
 import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ABORT;
 import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
@@ -42,7 +38,7 @@ import static org.briarproject.forum.InviteeSessionState.State.FINISHED;
 import static org.briarproject.forum.InviteeSessionState.State.LEFT;
 
 public class InviteeEngine
-		implements ProtocolEngine<Action, InviteeSessionState, BdfDictionary> {
+		implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
 
 	private final ForumFactory forumFactory;
 	private static final Logger LOG =
@@ -53,7 +49,7 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<InviteeSessionState, BdfDictionary> onLocalAction(
+	public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
 			InviteeSessionState localState, Action action) {
 
 		try {
@@ -72,37 +68,34 @@ public class InviteeEngine
 				}
 				return noUpdate(localState, true);
 			}
-			List<BdfDictionary> messages;
+			List<BaseMessage> messages;
 			List<Event> events = Collections.emptyList();
 
 			if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
-				BdfDictionary msg = BdfDictionary.of(
-						new BdfEntry(SESSION_ID, localState.getSessionId()),
-						new BdfEntry(GROUP_ID, localState.getGroupId())
-				);
+				BaseMessage msg;
 				if (action == LOCAL_ACCEPT) {
 					localState.setTask(TASK_ADD_SHARED_FORUM);
-					msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
+					msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
+							localState.getGroupId(), localState.getSessionId());
 				} else {
 					localState.setTask(
 							TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
-					msg.put(TYPE, SHARE_MSG_TYPE_DECLINE);
+					msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
+							localState.getGroupId(), localState.getSessionId());
 				}
 				messages = Collections.singletonList(msg);
 				logLocalAction(currentState, localState, msg);
 			}
 			else if (action == LOCAL_LEAVE) {
-				BdfDictionary msg = new BdfDictionary();
-				msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
-				msg.put(SESSION_ID, localState.getSessionId());
-				msg.put(GROUP_ID, localState.getGroupId());
+				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
+						localState.getGroupId(), localState.getSessionId());
 				messages = Collections.singletonList(msg);
 				logLocalAction(currentState, localState, msg);
 			}
 			else {
 				throw new IllegalArgumentException("Unknown Local Action");
 			}
-			return new StateUpdate<InviteeSessionState, BdfDictionary>(false,
+			return new StateUpdate<InviteeSessionState, BaseMessage>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -110,17 +103,16 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<InviteeSessionState, BdfDictionary> onMessageReceived(
-			InviteeSessionState localState, BdfDictionary msg) {
+	public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
+			InviteeSessionState localState, BaseMessage msg) {
 
 		try {
 			State currentState = localState.getState();
-			long type = msg.getLong(TYPE);
-			Action action = Action.getRemote(type);
+			Action action = Action.getRemote(msg.getType());
 			State nextState = currentState.next(action);
 			localState.setState(nextState);
 
-			logMessageReceived(currentState, nextState, type, msg);
+			logMessageReceived(currentState, nextState, msg.getType(), msg);
 
 			if (nextState == ERROR) {
 				if (currentState != ERROR) {
@@ -130,7 +122,7 @@ public class InviteeEngine
 				}
 			}
 
-			List<BdfDictionary> messages = Collections.emptyList();
+			List<BaseMessage> messages = Collections.emptyList();
 			List<Event> events = Collections.emptyList();
 			boolean deleteMsg = false;
 
@@ -164,7 +156,7 @@ public class InviteeEngine
 			else {
 				throw new IllegalArgumentException("Bad state");
 			}
-			return new StateUpdate<InviteeSessionState, BdfDictionary>(deleteMsg,
+			return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -172,50 +164,42 @@ public class InviteeEngine
 	}
 
 	private void logLocalAction(State state,
-			InviteeSessionState localState, BdfDictionary msg) {
+			InviteeSessionState localState, BaseMessage msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
 		String a = "response";
-		if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
-
-		try {
-			LOG.info("Sending " + a + " in state " + state.name() +
-					" with session ID " +
-					Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
-					Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
-					"Moving on to state " + localState.getState().name()
-			);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
+		if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
+
+		LOG.info("Sending " + a + " in state " + state.name() +
+				" with session ID " +
+				msg.getSessionId().hashCode() + " in group " +
+				msg.getGroupId().hashCode() + ". " +
+				"Moving on to state " + localState.getState().name()
+		);
 	}
 
 	private void logMessageReceived(State currentState, State nextState,
-			long type, BdfDictionary msg) {
+			long type, BaseMessage msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
-		try {
-			String t = "unknown";
-			if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
-			else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
-			else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
-
-			LOG.info("Received " + t + " in state " + currentState.name() +
-					" with session ID " +
-					Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
-					Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
-					"Moving on to state " + nextState.name()
-			);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
+		String t = "unknown";
+		if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
+		else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
+		else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
+
+		LOG.info("Received " + t + " in state " + currentState.name() +
+				" with session ID " +
+				msg.getSessionId().hashCode() + " in group " +
+				msg.getGroupId().hashCode() + ". " +
+				"Moving on to state " + nextState.name()
+		);
 	}
 
 	@Override
-	public StateUpdate<InviteeSessionState, BdfDictionary> onMessageDelivered(
-			InviteeSessionState localState, BdfDictionary delivered) {
+	public StateUpdate<InviteeSessionState, BaseMessage> onMessageDelivered(
+			InviteeSessionState localState, BaseMessage delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -224,7 +208,7 @@ public class InviteeEngine
 		}
 	}
 
-	private StateUpdate<InviteeSessionState, BdfDictionary> abortSession(
+	private StateUpdate<InviteeSessionState, BaseMessage> abortSession(
 			State currentState, InviteeSessionState localState)
 			throws FormatException {
 
@@ -233,25 +217,23 @@ public class InviteeEngine
 					localState.getSessionId().hashCode() +
 					" in state " + currentState.name());
 		}
-
 		localState.setState(ERROR);
-		BdfDictionary msg = new BdfDictionary();
-		msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
-		msg.put(SESSION_ID, localState.getSessionId());
-		msg.put(GROUP_ID, localState.getGroupId());
-		List<BdfDictionary> messages = Collections.singletonList(msg);
+		BaseMessage msg =
+				new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
+						localState.getSessionId());
+		List<BaseMessage> messages = Collections.singletonList(msg);
 
 		List<Event> events = Collections.emptyList();
 
-		return new StateUpdate<InviteeSessionState, BdfDictionary>(false, false,
+		return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<InviteeSessionState, BdfDictionary> noUpdate(
+	private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
 			InviteeSessionState localState, boolean delete) throws FormatException {
 
-		return new StateUpdate<InviteeSessionState, BdfDictionary>(delete, false,
-				localState, Collections.<BdfDictionary>emptyList(),
+		return new StateUpdate<InviteeSessionState, BaseMessage>(delete, false,
+				localState, Collections.<BaseMessage>emptyList(),
 				Collections.<Event>emptyList());
 	}
 }
diff --git a/briar-core/src/org/briarproject/forum/SharerEngine.java b/briar-core/src/org/briarproject/forum/SharerEngine.java
index a60437c5ebfb8949e6cf5b9dfb12f640a93d2ab1..4a142c039523413f5e18d982f51c55e62d3fbf0e 100644
--- a/briar-core/src/org/briarproject/forum/SharerEngine.java
+++ b/briar-core/src/org/briarproject/forum/SharerEngine.java
@@ -3,33 +3,27 @@ package org.briarproject.forum;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ProtocolEngine;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
-import static org.briarproject.forum.SharerSessionState.Action;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
-import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
 import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
 import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
 import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
 import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
 import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TYPE;
+import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
+import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
+import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
+import static org.briarproject.forum.SharerSessionState.Action;
 import static org.briarproject.forum.SharerSessionState.Action.LOCAL_ABORT;
 import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
 import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
@@ -42,13 +36,13 @@ import static org.briarproject.forum.SharerSessionState.State.FINISHED;
 import static org.briarproject.forum.SharerSessionState.State.LEFT;
 
 public class SharerEngine
-		implements ProtocolEngine<Action, SharerSessionState, BdfDictionary> {
+		implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
 
 	private static final Logger LOG =
 			Logger.getLogger(SharerEngine.class.getName());
 
 	@Override
-	public StateUpdate<SharerSessionState, BdfDictionary> onLocalAction(
+	public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
 			SharerSessionState localState, Action action) {
 
 		try {
@@ -67,19 +61,13 @@ public class SharerEngine
 				}
 				return noUpdate(localState, true);
 			}
-			List<BdfDictionary> messages;
+			List<BaseMessage> messages;
 			List<Event> events = Collections.emptyList();
 
 			if (action == LOCAL_INVITATION) {
-				BdfDictionary msg = new BdfDictionary();
-				msg.put(TYPE, SHARE_MSG_TYPE_INVITATION);
-				msg.put(SESSION_ID, localState.getSessionId());
-				msg.put(GROUP_ID, localState.getGroupId());
-				msg.put(FORUM_NAME, localState.getForumName());
-				msg.put(FORUM_SALT, localState.getForumSalt());
-				if (localState.getMessage() != null) {
-					msg.put(INVITATION_MSG, localState.getMessage());
-				}
+				BaseMessage msg = new Invitation(localState.getGroupId(),
+						localState.getSessionId(), localState.getForumName(),
+						localState.getForumSalt(), localState.getMessage());
 				messages = Collections.singletonList(msg);
 				logLocalAction(currentState, nextState, msg);
 
@@ -87,17 +75,15 @@ public class SharerEngine
 				localState.setTask(TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
 			}
 			else if (action == LOCAL_LEAVE) {
-				BdfDictionary msg = new BdfDictionary();
-				msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
-				msg.put(SESSION_ID, localState.getSessionId());
-				msg.put(GROUP_ID, localState.getGroupId());
+				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
+						localState.getGroupId(), localState.getSessionId());
 				messages = Collections.singletonList(msg);
 				logLocalAction(currentState, nextState, msg);
 			}
 			else {
 				throw new IllegalArgumentException("Unknown Local Action");
 			}
-			return new StateUpdate<SharerSessionState, BdfDictionary>(false,
+			return new StateUpdate<SharerSessionState, BaseMessage>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -105,17 +91,16 @@ public class SharerEngine
 	}
 
 	@Override
-	public StateUpdate<SharerSessionState, BdfDictionary> onMessageReceived(
-			SharerSessionState localState, BdfDictionary msg) {
+	public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
+			SharerSessionState localState, BaseMessage msg) {
 
 		try {
 			State currentState = localState.getState();
-			long type = msg.getLong(TYPE);
-			Action action = Action.getRemote(type);
+			Action action = Action.getRemote(msg.getType());
 			State nextState = currentState.next(action);
 			localState.setState(nextState);
 
-			logMessageReceived(currentState, nextState, type, msg);
+			logMessageReceived(currentState, nextState, msg.getType(), msg);
 
 			if (nextState == ERROR) {
 				if (currentState != ERROR) {
@@ -124,7 +109,7 @@ public class SharerEngine
 					return noUpdate(localState, true);
 				}
 			}
-			List<BdfDictionary> messages = Collections.emptyList();
+			List<BaseMessage> messages = Collections.emptyList();
 			List<Event> events = Collections.emptyList();
 			boolean deleteMsg = false;
 
@@ -157,7 +142,7 @@ public class SharerEngine
 			else {
 				throw new IllegalArgumentException("Bad state");
 			}
-			return new StateUpdate<SharerSessionState, BdfDictionary>(deleteMsg,
+			return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -165,51 +150,43 @@ public class SharerEngine
 	}
 
 	private void logLocalAction(State currentState, State nextState,
-			BdfDictionary msg) {
+			BaseMessage msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
 		String a = "invitation";
-		if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
-
-		try {
-			LOG.info("Sending " + a + " in state " + currentState.name() +
-					" with session ID " +
-					Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
-					Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
-					"Moving on to state " + nextState.name()
-			);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
+		if (msg.getType() == SHARE_MSG_TYPE_LEAVE) a = "leave";
+
+		LOG.info("Sending " + a + " in state " + currentState.name() +
+				" with session ID " +
+				msg.getSessionId().hashCode() + " in group " +
+				msg.getGroupId().hashCode() + ". " +
+				"Moving on to state " + nextState.name()
+		);
 	}
 
 	private void logMessageReceived(State currentState, State nextState,
-			long type, BdfDictionary msg) {
+			long type, BaseMessage msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
-		try {
-			String t = "unknown";
-			if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
-			else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
-			else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
-			else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
-
-			LOG.info("Received " + t + " in state " + currentState.name() +
-					" with session ID " +
-					Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
-					Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
-					"Moving on to state " + nextState.name()
-			);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
+		String t = "unknown";
+		if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
+		else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
+		else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
+		else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
+
+		LOG.info("Received " + t + " in state " + currentState.name() +
+				" with session ID " +
+				msg.getSessionId().hashCode() + " in group " +
+				msg.getGroupId().hashCode() + ". " +
+				"Moving on to state " + nextState.name()
+		);
 	}
 
 	@Override
-	public StateUpdate<SharerSessionState, BdfDictionary> onMessageDelivered(
-			SharerSessionState localState, BdfDictionary delivered) {
+	public StateUpdate<SharerSessionState, BaseMessage> onMessageDelivered(
+			SharerSessionState localState, BaseMessage delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -218,7 +195,7 @@ public class SharerEngine
 		}
 	}
 
-	private StateUpdate<SharerSessionState, BdfDictionary> abortSession(
+	private StateUpdate<SharerSessionState, BaseMessage> abortSession(
 			State currentState, SharerSessionState localState)
 			throws FormatException {
 
@@ -229,24 +206,22 @@ public class SharerEngine
 		}
 
 		localState.setState(ERROR);
-		BdfDictionary msg = new BdfDictionary();
-		msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
-		msg.put(SESSION_ID, localState.getSessionId());
-		msg.put(GROUP_ID, localState.getGroupId());
-		List<BdfDictionary> messages = Collections.singletonList(msg);
+		BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
+				localState.getGroupId(), localState.getSessionId());
+		List<BaseMessage> messages = Collections.singletonList(msg);
 
 		List<Event> events = Collections.emptyList();
 
-		return new StateUpdate<SharerSessionState, BdfDictionary>(false, false,
+		return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<SharerSessionState, BdfDictionary> noUpdate(
+	private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
 			SharerSessionState localState, boolean delete)
 			throws FormatException {
 
-		return new StateUpdate<SharerSessionState, BdfDictionary>(delete, false,
-				localState, Collections.<BdfDictionary>emptyList(),
+		return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
+				localState, Collections.<BaseMessage>emptyList(),
 				Collections.<Event>emptyList());
 	}