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-api/src/org/briarproject/api/forum/InviteeAction.java b/briar-api/src/org/briarproject/api/forum/InviteeAction.java
deleted file mode 100644
index 212f0861ce661de2e69b18adeb8a52ba9aa14af5..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/forum/InviteeAction.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.briarproject.api.forum;
-
-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;
-
-public enum InviteeAction {
-
-	LOCAL_ACCEPT,
-	LOCAL_DECLINE,
-	LOCAL_LEAVE,
-	LOCAL_ABORT,
-	REMOTE_INVITATION,
-	REMOTE_LEAVE,
-	REMOTE_ABORT;
-
-	public static InviteeAction getLocal(long type) {
-		if (type == SHARE_MSG_TYPE_ACCEPT) return LOCAL_ACCEPT;
-		if (type == SHARE_MSG_TYPE_DECLINE) return LOCAL_DECLINE;
-		if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
-		if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
-		return null;
-	}
-
-	public static InviteeAction getRemote(long type) {
-		if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
-		if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
-		if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
-		return null;
-	}
-
-}
diff --git a/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java b/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java
deleted file mode 100644
index 35d6a880af0b9bbb41c3ffd2ede3122d9b7fb297..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.briarproject.api.forum;
-
-import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
-import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
-import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
-import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
-import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
-
-public enum InviteeProtocolState {
-
-	ERROR(0),
-	AWAIT_INVITATION(1) {
-		@Override
-		public InviteeProtocolState next(InviteeAction a) {
-			if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
-			return ERROR;
-		}
-	},
-	AWAIT_LOCAL_RESPONSE(2) {
-		@Override
-		public InviteeProtocolState next(InviteeAction a) {
-			if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
-			if (a == REMOTE_LEAVE) return LEFT;
-			return ERROR;
-		}
-	},
-	FINISHED(3) {
-		@Override
-		public InviteeProtocolState next(InviteeAction a) {
-			if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
-			return FINISHED;
-		}
-	},
-	LEFT(4) {
-		@Override
-		public InviteeProtocolState next(InviteeAction a) {
-			if (a == LOCAL_LEAVE) return ERROR;
-			return LEFT;
-		}
-	};
-
-	private final int value;
-
-	InviteeProtocolState(int value) {
-		this.value = value;
-	}
-
-	public int getValue() {
-		return value;
-	}
-
-	public static InviteeProtocolState fromValue(int value) {
-		for (InviteeProtocolState s : values()) {
-			if (s.value == value) return s;
-		}
-		throw new IllegalArgumentException();
-	}
-
-	public InviteeProtocolState next(InviteeAction a) {
-		return this;
-	}
-}
diff --git a/briar-api/src/org/briarproject/api/forum/SharerAction.java b/briar-api/src/org/briarproject/api/forum/SharerAction.java
deleted file mode 100644
index 39796f2c828c10f01bcd6ae3a05f7d258792e11e..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/forum/SharerAction.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.briarproject.api.forum;
-
-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;
-
-public enum SharerAction {
-
-	LOCAL_INVITATION,
-	LOCAL_LEAVE,
-	LOCAL_ABORT,
-	REMOTE_ACCEPT,
-	REMOTE_DECLINE,
-	REMOTE_LEAVE,
-	REMOTE_ABORT;
-
-	public static SharerAction getLocal(long type) {
-		if (type == SHARE_MSG_TYPE_INVITATION) return LOCAL_INVITATION;
-		if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
-		if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
-		return null;
-	}
-
-	public static SharerAction getRemote(long type) {
-		if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
-		if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
-		if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
-		if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
-		return null;
-	}
-
-}
diff --git a/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java b/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java
deleted file mode 100644
index b948a9483d980d1cd9a0eeed91d81a6edcdf5511..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.briarproject.api.forum;
-
-import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
-import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
-import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
-import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
-import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
-
-public enum SharerProtocolState {
-
-	ERROR(0),
-	PREPARE_INVITATION(1) {
-		@Override
-		public SharerProtocolState next(SharerAction a) {
-			if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
-			return ERROR;
-		}
-	},
-	AWAIT_RESPONSE(2) {
-		@Override
-		public SharerProtocolState next(SharerAction a) {
-			if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
-			if (a == LOCAL_LEAVE) return LEFT;
-			return ERROR;
-		}
-	},
-	FINISHED(3) {
-		@Override
-		public SharerProtocolState next(SharerAction a) {
-			if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
-			return FINISHED;
-		}
-	},
-	LEFT(4) {
-		@Override
-		public SharerProtocolState next(SharerAction a) {
-			if (a == LOCAL_LEAVE) return ERROR;
-			return LEFT;
-		}
-	};
-
-	private final int value;
-
-	SharerProtocolState(int value) {
-		this.value = value;
-	}
-
-	public int getValue() {
-		return value;
-	}
-
-	public static SharerProtocolState fromValue(int value) {
-		for (SharerProtocolState s : values()) {
-			if (s.value == value) return s;
-		}
-		throw new IllegalArgumentException();
-	}
-
-	public SharerProtocolState next(SharerAction a) {
-		return this;
-	}
-}
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
index a157dbe8abd7c5287a0e267ae7bebc6a92614976..cbeb56d60f2ecf2c14000f897ef145c849bea0c8 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
@@ -27,10 +27,6 @@ import org.briarproject.api.forum.ForumFactory;
 import org.briarproject.api.forum.ForumInvitationMessage;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.forum.InviteeAction;
-import org.briarproject.api.forum.InviteeProtocolState;
-import org.briarproject.api.forum.SharerAction;
-import org.briarproject.api.forum.SharerProtocolState;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -59,12 +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_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;
@@ -76,11 +67,8 @@ 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.STATE;
-import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
-import static org.briarproject.api.forum.ForumConstants.TASK;
-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_ADD_FORUM_TO_LIST_SHARED_WITH_US;
+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_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_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
@@ -91,9 +79,10 @@ 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.InviteeProtocolState.AWAIT_INVITATION;
-import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_LOCAL_RESPONSE;
-import static org.briarproject.api.forum.SharerProtocolState.PREPARE_INVITATION;
+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;
 
 class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		implements ForumSharingManager, Client, RemoveForumHook,
@@ -187,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 {
+
+		BaseMessage msg = BaseMessage.from(m.getGroupId(), d);
+		SessionId sessionId = msg.getSessionId();
 
-		SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
-		long type = msg.getLong(TYPE);
-		if (type == SHARE_MSG_TYPE_INVITATION) {
+		if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
 			// we are an invitee who just received a new invitation
 			boolean stateExists = true;
 			try {
@@ -206,43 +196,46 @@ 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))
 					throw new FormatException();
 
 				// initialize state and process invitation
-				BdfDictionary state =
-						initializeInviteeState(txn, contactId, msg);
+				InviteeSessionState state =
+						initializeInviteeState(txn, contactId, invitation);
 				InviteeEngine engine = new InviteeEngine(forumFactory);
-				processStateUpdate(txn, m.getId(),
+				processInviteeStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			} catch (FormatException e) {
 				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
-			BdfDictionary state = getSessionState(txn, sessionId, true);
+			SharerSessionState state = getSessionStateForSharer(txn, sessionId);
 			SharerEngine engine = new SharerEngine();
-			processStateUpdate(txn, m.getId(),
+			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
-			BdfDictionary state = getSessionState(txn, sessionId, true);
-			if (state.getBoolean(IS_SHARER)) {
+			ForumSharingSessionState s = getSessionState(txn, sessionId, true);
+			if (s instanceof SharerSessionState) {
 				// we are a sharer and the invitee wants to leave or abort
+				SharerSessionState state = (SharerSessionState) s;
 				SharerEngine engine = new SharerEngine();
-				processStateUpdate(txn, m.getId(),
+				processSharerStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			} else {
 				// we are an invitee and the sharer wants to leave or abort
+				InviteeSessionState state = (InviteeSessionState) s;
 				InviteeEngine engine = new InviteeEngine(forumFactory);
-				processStateUpdate(txn, m.getId(),
+				processInviteeStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			}
 		} else {
@@ -264,19 +257,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		try {
 			// initialize local state for sharer
 			Forum f = forumManager.getForum(txn, groupId);
-			BdfDictionary localState = initializeSharerState(txn, f, contactId);
+			SharerSessionState localState =
+					initializeSharerState(txn, f, contactId);
 
-			// define action
-			BdfDictionary localAction = new BdfDictionary();
-			localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION);
+			// add invitation message to local state to be available for engine
 			if (!StringUtils.isNullOrEmpty(msg)) {
-				localAction.put(INVITATION_MSG, msg);
+				localState.setMessage(msg);
 			}
 
 			// start engine and process its state update
 			SharerEngine engine = new SharerEngine();
-			processStateUpdate(txn, null,
-					engine.onLocalAction(localState, localAction));
+			processSharerStateUpdate(txn, null,
+					engine.onLocalAction(localState, Action.LOCAL_INVITATION));
 
 			txn.setComplete();
 		} catch (FormatException e) {
@@ -293,19 +285,19 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		Transaction txn = db.startTransaction(false);
 		try {
 			// find session state based on forum
-			BdfDictionary localState = getSessionStateForResponse(txn, f);
+			InviteeSessionState localState = getSessionStateForResponse(txn, f);
 
 			// define action
-			BdfDictionary localAction = new BdfDictionary();
+			InviteeSessionState.Action localAction;
 			if (accept) {
-				localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
+				localAction = InviteeSessionState.Action.LOCAL_ACCEPT;
 			} else {
-				localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE);
+				localAction = InviteeSessionState.Action.LOCAL_DECLINE;
 			}
 
 			// start engine and process its state update
 			InviteeEngine engine = new InviteeEngine(forumFactory);
-			processStateUpdate(txn, null,
+			processInviteeStateUpdate(txn, null,
 					engine.onLocalAction(localState, localAction));
 
 			txn.setComplete();
@@ -330,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
-						BdfDictionary sessionState =
-								getSessionState(txn, sessionId, true);
-						InviteeProtocolState state = InviteeProtocolState
-								.fromValue(
-										sessionState.getLong(STATE).intValue());
-						available = state == AWAIT_LOCAL_RESPONSE;
+						ForumSharingSessionState s =
+								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))
@@ -490,7 +481,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private BdfDictionary initializeSharerState(Transaction txn, Forum f,
+	private SharerSessionState initializeSharerState(Transaction txn, Forum f,
 			ContactId contactId) throws FormatException, DbException {
 
 		Contact c = db.getContact(txn, contactId);
@@ -502,33 +493,27 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		random.nextBytes(salt.getBytes());
 		Message m = clientHelper.createMessage(localGroup.getId(), now,
 				BdfList.of(salt));
-		MessageId sessionId = m.getId();
-
-		BdfDictionary d = new BdfDictionary();
-		d.put(SESSION_ID, sessionId);
-		d.put(STORAGE_ID, sessionId);
-		d.put(GROUP_ID, group.getId());
-		d.put(IS_SHARER, true);
-		d.put(STATE, PREPARE_INVITATION.getValue());
-		d.put(CONTACT_ID, contactId.getInt());
-		d.put(FORUM_ID, f.getId());
-		d.put(FORUM_NAME, f.getName());
-		d.put(FORUM_SALT, f.getSalt());
+		SessionId sessionId = new SessionId(m.getId().getBytes());
+
+		SharerSessionState s = new SharerSessionState(sessionId, sessionId,
+				group.getId(), SharerSessionState.State.PREPARE_INVITATION,
+				contactId, f.getId(), f.getName(), f.getSalt());
 
 		// save local state to database
+		BdfDictionary d = s.toBdfDictionary();
 		clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
 
-		return d;
+		return s;
 	}
 
-	private BdfDictionary initializeInviteeState(Transaction txn,
-			ContactId contactId, BdfDictionary msg)
+	private InviteeSessionState initializeInviteeState(Transaction txn,
+			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
@@ -538,29 +523,24 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		Message m = clientHelper.createMessage(localGroup.getId(), now,
 				BdfList.of(mSalt));
 
-		BdfDictionary d = new BdfDictionary();
-		d.put(SESSION_ID, msg.getRaw(SESSION_ID));
-		d.put(STORAGE_ID, m.getId());
-		d.put(GROUP_ID, group.getId());
-		d.put(IS_SHARER, false);
-		d.put(STATE, AWAIT_INVITATION.getValue());
-		d.put(CONTACT_ID, contactId.getInt());
-		d.put(FORUM_ID, f.getId());
-		d.put(FORUM_NAME, name);
-		d.put(FORUM_SALT, salt);
+		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();
 		clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
 
-		return d;
+		return s;
 	}
 
-	private BdfDictionary getSessionState(Transaction txn, SessionId sessionId,
-			boolean warn) throws DbException, FormatException {
+	private ForumSharingSessionState getSessionState(Transaction txn,
+			SessionId sessionId, boolean warn)
+			throws DbException, FormatException {
 
 		try {
-			// we should be able to get the sharer state directly from sessionId
-			return clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
+			return getSessionStateForSharer(txn, sessionId);
 		} catch (NoSuchMessageException e) {
 			// State not found directly, so iterate over all states
 			// to find state for invitee
@@ -570,7 +550,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				BdfDictionary state = m.getValue();
 				if (Arrays.equals(state.getRaw(SESSION_ID),
 						sessionId.getBytes())) {
-					return state;
+					return fromBdfDictionary(state);
 				}
 			}
 			if (warn && LOG.isLoggable(WARNING)) {
@@ -582,23 +562,37 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private BdfDictionary getSessionStateForResponse(Transaction txn, Forum f)
+	private SharerSessionState getSessionStateForSharer(Transaction txn,
+			SessionId sessionId)
 			throws DbException, FormatException {
 
+		// we should be able to get the sharer state directly from sessionId
+		BdfDictionary d =
+				clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
+
+		if (!d.getBoolean(IS_SHARER)) throw new FormatException();
+
+		return (SharerSessionState) fromBdfDictionary(d);
+	}
+
+	private InviteeSessionState getSessionStateForResponse(Transaction txn,
+			Forum f) throws DbException, FormatException {
+
 		Map<MessageId, BdfDictionary> map = clientHelper
 				.getMessageMetadataAsDictionary(txn, localGroup.getId());
 		for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
 			BdfDictionary d = m.getValue();
 			try {
-				InviteeProtocolState state = InviteeProtocolState
-						.fromValue(d.getLong(STATE).intValue());
-				if (state == AWAIT_LOCAL_RESPONSE) {
-					byte[] id = d.getRaw(FORUM_ID);
-					if (Arrays.equals(f.getId().getBytes(), id)) {
-						// Note that there should always be only one session
-						// in this state for the same forum
-						return d;
-					}
+				ForumSharingSessionState s = fromBdfDictionary(d);
+				if (!(s instanceof InviteeSessionState)) continue;
+				if (!f.getId().equals(s.getForumId())) continue;
+
+				InviteeSessionState state = (InviteeSessionState) s;
+				if (state.getState() ==
+						InviteeSessionState.State.AWAIT_LOCAL_RESPONSE) {
+					// Note that there should always be only one session
+					// in this state for the same forum
+					return state;
 				}
 			} catch (FormatException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -607,36 +601,40 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		throw new DbException();
 	}
 
-	private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f,
-			ContactId c) throws DbException, FormatException {
+	private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,
+			Forum f, ContactId c) throws DbException, FormatException {
 
 		Map<MessageId, BdfDictionary> map = clientHelper
 				.getMessageMetadataAsDictionary(txn, localGroup.getId());
 		for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
 			BdfDictionary d = m.getValue();
 			try {
+				ForumSharingSessionState s = fromBdfDictionary(d);
+
 				// check that this session is with the right contact
-				if (c.getInt() != d.getLong(CONTACT_ID)) continue;
+				if (!c.equals(s.getContactId())) continue;
+
+				// check that this state actually concerns this forum
+				if (!s.getForumName().equals(f.getName()) ||
+						!Arrays.equals(s.getForumSalt(), f.getSalt())) {
+					continue;
+				}
+
 				// check that a forum get be left in current session
-				int intState = d.getLong(STATE).intValue();
-				if (d.getBoolean(IS_SHARER)) {
-					SharerProtocolState state =
-							SharerProtocolState.fromValue(intState);
-					if (state.next(SharerAction.LOCAL_LEAVE) ==
-							SharerProtocolState.ERROR) continue;
+				if (s instanceof SharerSessionState) {
+					SharerSessionState state = (SharerSessionState) s;
+					SharerSessionState.State nextState =
+							state.getState().next(Action.LOCAL_LEAVE);
+					if (nextState != SharerSessionState.State.ERROR) {
+						return state;
+					}
 				} else {
-					InviteeProtocolState state = InviteeProtocolState
-							.fromValue(intState);
-					if (state.next(InviteeAction.LOCAL_LEAVE) ==
-							InviteeProtocolState.ERROR) continue;
-				}
-				// check that this state actually concerns this forum
-				String name = d.getString(FORUM_NAME);
-				byte[] salt = d.getRaw(FORUM_SALT);
-				if (name.equals(f.getName()) &&
-						Arrays.equals(salt, f.getSalt())) {
-					// TODO what happens when there is more than one invitation?
-					return d;
+					InviteeSessionState state = (InviteeSessionState) s;
+					InviteeSessionState.State nextState = state.getState()
+							.next(InviteeSessionState.Action.LOCAL_LEAVE);
+					if (nextState != InviteeSessionState.State.ERROR) {
+						return state;
+					}
 				}
 			} catch (FormatException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -646,20 +644,20 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<BdfDictionary, BdfDictionary> result)
+			StateUpdate<ForumSharingSessionState, BaseMessage> result)
 			throws DbException, FormatException {
 
 		// perform actions based on new local state
 		performTasks(txn, result.localState);
 
 		// save new local state
-		MessageId storageId =
-				new MessageId(result.localState.getRaw(STORAGE_ID));
-		clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
+		MessageId storageId = result.localState.getStorageId();
+		clientHelper.mergeMessageMetadata(txn, storageId,
+				result.localState.toBdfDictionary());
 
 		// send messages
-		for (BdfDictionary d : result.toSend) {
-			sendMessage(txn, d);
+		for (BaseMessage msg : result.toSend) {
+			sendMessage(txn, msg);
 		}
 
 		// broadcast events
@@ -677,24 +675,47 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private void performTasks(Transaction txn, BdfDictionary localState)
+	private void processSharerStateUpdate(Transaction txn, MessageId messageId,
+			StateUpdate<SharerSessionState, BaseMessage> result)
+			throws DbException, FormatException {
+
+		StateUpdate<ForumSharingSessionState, BaseMessage> r =
+				new StateUpdate<ForumSharingSessionState, BaseMessage>(
+						result.deleteMessage, result.deleteState,
+						result.localState, result.toSend, result.toBroadcast);
+
+		processStateUpdate(txn, messageId, r);
+	}
+
+	private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
+			StateUpdate<InviteeSessionState, BaseMessage> result)
+			throws DbException, FormatException {
+
+		StateUpdate<ForumSharingSessionState, BaseMessage> r =
+				new StateUpdate<ForumSharingSessionState, BaseMessage>(
+						result.deleteMessage, result.deleteState,
+						result.localState, result.toSend, result.toBroadcast);
+
+		processStateUpdate(txn, messageId, r);
+	}
+
+	private void performTasks(Transaction txn, ForumSharingSessionState localState)
 			throws FormatException, DbException {
 
-		if (!localState.containsKey(TASK)) return;
+		if (localState.getTask() == -1) return;
 
 		// remember task and remove it from localState
-		long task = localState.getLong(TASK);
-		localState.put(TASK, BdfDictionary.NULL_VALUE);
+		long task = localState.getTask();
+		localState.setTask(-1);
 
 		// get group ID for later
-		GroupId groupId = new GroupId(localState.getRaw(GROUP_ID));
+		GroupId groupId = localState.getGroupId();
 		// get contact ID for later
-		ContactId contactId =
-				new ContactId(localState.getLong(CONTACT_ID).intValue());
+		ContactId contactId = localState.getContactId();
 
 		// get forum for later
-		String name = localState.getString(FORUM_NAME);
-		byte[] salt = localState.getRaw(FORUM_SALT);
+		String name = localState.getForumName();
+		byte[] salt = localState.getForumSalt();
 		Forum f = forumFactory.createForum(name, salt);
 
 		// perform tasks
@@ -728,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);
 	}
@@ -786,17 +780,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	private void leaveForum(Transaction txn, ContactId c, Forum f)
 			throws DbException, FormatException {
 
-		BdfDictionary state = getSessionStateForLeaving(txn, f, c);
-		BdfDictionary action = new BdfDictionary();
-		action.put(TYPE, SHARE_MSG_TYPE_LEAVE);
-		if (state.getBoolean(IS_SHARER)) {
+		ForumSharingSessionState state = getSessionStateForLeaving(txn, f, c);
+		if (state instanceof SharerSessionState) {
+			Action action = Action.LOCAL_LEAVE;
 			SharerEngine engine = new SharerEngine();
-			processStateUpdate(txn, null,
-					engine.onLocalAction(state, action));
+			processSharerStateUpdate(txn, null,
+					engine.onLocalAction((SharerSessionState) state, action));
 		} else {
+			InviteeSessionState.Action action =
+					InviteeSessionState.Action.LOCAL_LEAVE;
 			InviteeEngine engine = new InviteeEngine(forumFactory);
-			processStateUpdate(txn, null,
-					engine.onLocalAction(state, action));
+			processInviteeStateUpdate(txn, null,
+					engine.onLocalAction((InviteeSessionState) state, action));
 		}
 	}
 
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java b/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc2e69bfb38ecbc5ec72f1d83080c1618207f3ad
--- /dev/null
+++ b/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java
@@ -0,0 +1,119 @@
+package org.briarproject.forum;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
+import static org.briarproject.api.forum.ForumConstants.FORUM_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.GROUP_ID;
+import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
+import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
+import static org.briarproject.api.forum.ForumConstants.STATE;
+import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
+
+// This class is not thread-safe
+public abstract class ForumSharingSessionState {
+
+	private final SessionId sessionId;
+	private final MessageId storageId;
+	private final GroupId groupId;
+	private final ContactId contactId;
+	private final GroupId forumId;
+	private final String forumName;
+	private final byte[] forumSalt;
+	private int task = -1; // TODO get rid of task, see #376
+
+	public ForumSharingSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, ContactId contactId, GroupId forumId,
+			String forumName, byte[] forumSalt) {
+
+		this.sessionId = sessionId;
+		this.storageId = storageId;
+		this.groupId = groupId;
+		this.contactId = contactId;
+		this.forumId = forumId;
+		this.forumName = forumName;
+		this.forumSalt = forumSalt;
+	}
+
+	public static ForumSharingSessionState fromBdfDictionary(BdfDictionary d)
+			throws FormatException{
+
+		SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
+		MessageId messageId = new MessageId(d.getRaw(STORAGE_ID));
+		GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
+		ContactId contactId = new ContactId(d.getLong(CONTACT_ID).intValue());
+		GroupId forumId = new GroupId(d.getRaw(FORUM_ID));
+		String forumName = d.getString(FORUM_NAME);
+		byte[] forumSalt = d.getRaw(FORUM_SALT);
+
+		int intState = d.getLong(STATE).intValue();
+		if (d.getBoolean(IS_SHARER)) {
+			SharerSessionState.State state =
+					SharerSessionState.State.fromValue(intState);
+			return new SharerSessionState(sessionId, messageId, groupId, state,
+					contactId, forumId, forumName, forumSalt);
+		} else {
+			InviteeSessionState.State state =
+					InviteeSessionState.State.fromValue(intState);
+			return new InviteeSessionState(sessionId, messageId, groupId, state,
+					contactId, forumId, forumName, forumSalt);
+		}
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = new BdfDictionary();
+		d.put(SESSION_ID, getSessionId());
+		d.put(STORAGE_ID, getStorageId());
+		d.put(GROUP_ID, getGroupId());
+		d.put(CONTACT_ID, getContactId().getInt());
+		d.put(FORUM_ID, getForumId());
+		d.put(FORUM_NAME, getForumName());
+		d.put(FORUM_SALT, getForumSalt());
+
+		return d;
+	}
+
+	public SessionId getSessionId() {
+		return sessionId;
+	}
+
+	public MessageId getStorageId() {
+		return storageId;
+	}
+
+	public GroupId getGroupId() {
+		return groupId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+
+	public GroupId getForumId() {
+		return forumId;
+	}
+
+	public String getForumName() {
+		return forumName;
+	}
+
+	public byte[] getForumSalt() {
+		return forumSalt;
+	}
+
+	public void setTask(int task) {
+		this.task = task;
+	}
+
+	public int getTask() {
+		return task;
+	}
+
+}
\ No newline at end of file
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 70e9a24cb4edaeccf2ae96336d905bbc55d0d139..c7c9ff6223c159fb163d6e6504d81c5600cc4246 100644
--- a/briar-core/src/org/briarproject/forum/InviteeEngine.java
+++ b/briar-core/src/org/briarproject/forum/InviteeEngine.java
@@ -3,51 +3,42 @@ 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 org.briarproject.api.forum.InviteeAction;
-import org.briarproject.api.forum.InviteeProtocolState;
 
-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.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.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;
 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.STATE;
-import static org.briarproject.api.forum.ForumConstants.TASK;
 import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
 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.InviteeAction.LOCAL_ABORT;
-import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
-import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
-import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
-import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
-import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
-import static org.briarproject.api.forum.InviteeProtocolState.ERROR;
-import static org.briarproject.api.forum.InviteeProtocolState.FINISHED;
-import static org.briarproject.api.forum.InviteeProtocolState.LEFT;
+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;
+import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
+import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
+import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
+import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
+import static org.briarproject.forum.InviteeSessionState.State;
+import static org.briarproject.forum.InviteeSessionState.State.ERROR;
+import static org.briarproject.forum.InviteeSessionState.State.FINISHED;
+import static org.briarproject.forum.InviteeSessionState.State.LEFT;
 
 public class InviteeEngine
-		implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
+		implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
 
 	private final ForumFactory forumFactory;
 	private static final Logger LOG =
@@ -58,16 +49,13 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
-			BdfDictionary localState, BdfDictionary localAction) {
+	public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
+			InviteeSessionState localState, Action action) {
 
 		try {
-			InviteeProtocolState currentState =
-					getState(localState.getLong(STATE));
-			long type = localAction.getLong(TYPE);
-			InviteeAction action = InviteeAction.getLocal(type);
-			InviteeProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			State currentState = localState.getState();
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			if (action == LOCAL_ABORT && currentState != ERROR) {
 				return abortSession(currentState, localState);
@@ -80,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.getRaw(SESSION_ID)),
-						new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID))
-				);
+				BaseMessage msg;
 				if (action == LOCAL_ACCEPT) {
-					localState.put(TASK, TASK_ADD_SHARED_FORUM);
-					msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
+					localState.setTask(TASK_ADD_SHARED_FORUM);
+					msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
+							localState.getGroupId(), localState.getSessionId());
 				} else {
-					localState.put(TASK,
+					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.getRaw(SESSION_ID));
-				msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
+				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<BdfDictionary, BdfDictionary>(false,
+			return new StateUpdate<InviteeSessionState, BaseMessage>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -118,18 +103,16 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
-			BdfDictionary localState, BdfDictionary msg) {
+	public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
+			InviteeSessionState localState, BaseMessage msg) {
 
 		try {
-			InviteeProtocolState currentState =
-					getState(localState.getLong(STATE));
-			long type = msg.getLong(TYPE);
-			InviteeAction action = InviteeAction.getRemote(type);
-			InviteeProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			State currentState = localState.getState();
+			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) {
@@ -139,7 +122,7 @@ public class InviteeEngine
 				}
 			}
 
-			List<BdfDictionary> messages = Collections.emptyList();
+			List<BaseMessage> messages = Collections.emptyList();
 			List<Event> events = Collections.emptyList();
 			boolean deleteMsg = false;
 
@@ -149,7 +132,7 @@ public class InviteeEngine
 			}
 			// the sharer left the forum she had shared with us
 			else if (action == REMOTE_LEAVE && currentState == FINISHED) {
-				localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US);
+				localState.setTask(TASK_UNSHARE_FORUM_SHARED_WITH_US);
 			}
 			else if (currentState == FINISHED) {
 				// ignore and delete messages coming in while in that state
@@ -158,74 +141,65 @@ public class InviteeEngine
 			}
 			// the sharer left the forum before we couldn't even respond
 			else if (action == REMOTE_LEAVE) {
-				localState.put(TASK, TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
+				localState.setTask(TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
 			}
 			// we have just received our invitation
 			else if (action == REMOTE_INVITATION) {
-				localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
 				Forum forum = forumFactory
-						.createForum(localState.getString(FORUM_NAME),
-								localState.getRaw(FORUM_SALT));
-				ContactId contactId = new ContactId(
-						localState.getLong(CONTACT_ID).intValue());
+						.createForum(localState.getForumName(),
+								localState.getForumSalt());
+				localState.setTask(TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
+				ContactId contactId = localState.getContactId();
 				Event event = new ForumInvitationReceivedEvent(forum, contactId);
 				events = Collections.singletonList(event);
 			}
 			else {
 				throw new IllegalArgumentException("Bad state");
 			}
-			return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
+			return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
 		}
 	}
 
-	private void logLocalAction(InviteeProtocolState state,
-			BdfDictionary localState, BdfDictionary msg) {
+	private void logLocalAction(State state,
+			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 " +
-					getState(localState.getLong(STATE)).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(InviteeProtocolState currentState,
-			InviteeProtocolState nextState, long type, BdfDictionary msg) {
+	private void logMessageReceived(State currentState, State nextState,
+			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<BdfDictionary, BdfDictionary> onMessageDelivered(
-			BdfDictionary localState, BdfDictionary delivered) {
+	public StateUpdate<InviteeSessionState, BaseMessage> onMessageDelivered(
+			InviteeSessionState localState, BaseMessage delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -234,38 +208,32 @@ public class InviteeEngine
 		}
 	}
 
-	private InviteeProtocolState getState(Long state) {
-		return InviteeProtocolState.fromValue(state.intValue());
-	}
-
-	private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
-			InviteeProtocolState currentState, BdfDictionary localState)
+	private StateUpdate<InviteeSessionState, BaseMessage> abortSession(
+			State currentState, InviteeSessionState localState)
 			throws FormatException {
 
 		if (LOG.isLoggable(WARNING)) {
 			LOG.warning("Aborting protocol session " +
-					Arrays.hashCode(localState.getRaw(SESSION_ID)) +
+					localState.getSessionId().hashCode() +
 					" in state " + currentState.name());
 		}
-
-		localState.put(STATE, ERROR.getValue());
-		BdfDictionary msg = new BdfDictionary();
-		msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
-		msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
-		msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
-		List<BdfDictionary> messages = Collections.singletonList(msg);
+		localState.setState(ERROR);
+		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<BdfDictionary, BdfDictionary>(false, false,
+		return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
-			BdfDictionary localState, boolean delete) throws FormatException {
+	private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
+			InviteeSessionState localState, boolean delete) throws FormatException {
 
-		return new StateUpdate<BdfDictionary, 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/InviteeSessionState.java b/briar-core/src/org/briarproject/forum/InviteeSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..68b397bc726767ecc15b52f72526425470908725
--- /dev/null
+++ b/briar-core/src/org/briarproject/forum/InviteeSessionState.java
@@ -0,0 +1,120 @@
+package org.briarproject.forum;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
+import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
+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.STATE;
+import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
+import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
+import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
+import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
+import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
+
+// This class is not thread-safe
+public class InviteeSessionState extends ForumSharingSessionState {
+
+	private State state;
+
+	public InviteeSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId forumId,
+			String forumName, byte[] forumSalt) {
+
+		super(sessionId, storageId, groupId, contactId, forumId, forumName,
+				forumSalt);
+		this.state = state;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(STATE, getState().getValue());
+		d.put(IS_SHARER, false);
+		return d;
+	}
+
+	public void setState(State state) {
+		this.state = state;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public enum State {
+		ERROR(0),
+		AWAIT_INVITATION(1) {
+			@Override
+			public State next(Action a) {
+				if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
+				return ERROR;
+			}
+		},
+		AWAIT_LOCAL_RESPONSE(2) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
+				if (a == REMOTE_LEAVE) return LEFT;
+				return ERROR;
+			}
+		},
+		FINISHED(3) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
+				return FINISHED;
+			}
+		},
+		LEFT(4) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_LEAVE) return ERROR;
+				return LEFT;
+			}
+		};
+
+		private final int value;
+
+		State(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public static State fromValue(int value) {
+			for (State s : values()) {
+				if (s.value == value) return s;
+			}
+			throw new IllegalArgumentException();
+		}
+
+		public State next(Action a) {
+			return this;
+		}
+	}
+
+	public enum Action {
+		LOCAL_ACCEPT,
+		LOCAL_DECLINE,
+		LOCAL_LEAVE,
+		LOCAL_ABORT,
+		REMOTE_INVITATION,
+		REMOTE_LEAVE,
+		REMOTE_ABORT;
+
+		public static Action getRemote(long type) {
+			if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
+			if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
+			if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
+			return null;
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/briar-core/src/org/briarproject/forum/SharerEngine.java b/briar-core/src/org/briarproject/forum/SharerEngine.java
index 05db965b80fb8f78aaa7b86c3f67cae3a838759f..4a142c039523413f5e18d982f51c55e62d3fbf0e 100644
--- a/briar-core/src/org/briarproject/forum/SharerEngine.java
+++ b/briar-core/src/org/briarproject/forum/SharerEngine.java
@@ -3,64 +3,52 @@ 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 org.briarproject.api.forum.SharerAction;
-import org.briarproject.api.forum.SharerProtocolState;
 
-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.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.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.STATE;
-import static org.briarproject.api.forum.ForumConstants.TASK;
 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.SharerAction.LOCAL_ABORT;
-import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
-import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
-import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
-import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
-import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
-import static org.briarproject.api.forum.SharerProtocolState.ERROR;
-import static org.briarproject.api.forum.SharerProtocolState.FINISHED;
-import static org.briarproject.api.forum.SharerProtocolState.LEFT;
+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;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
+import static org.briarproject.forum.SharerSessionState.State;
+import static org.briarproject.forum.SharerSessionState.State.ERROR;
+import static org.briarproject.forum.SharerSessionState.State.FINISHED;
+import static org.briarproject.forum.SharerSessionState.State.LEFT;
 
 public class SharerEngine
-		implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
+		implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
 
 	private static final Logger LOG =
 			Logger.getLogger(SharerEngine.class.getName());
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
-			BdfDictionary localState, BdfDictionary localAction) {
+	public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
+			SharerSessionState localState, Action action) {
 
 		try {
-			SharerProtocolState currentState =
-					getState(localState.getLong(STATE));
-			long type = localAction.getLong(TYPE);
-			SharerAction action = SharerAction.getLocal(type);
-			SharerProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			State currentState = localState.getState();
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			if (action == LOCAL_ABORT && currentState != ERROR) {
 				return abortSession(currentState, localState);
@@ -73,38 +61,29 @@ 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.getRaw(SESSION_ID));
-				msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
-				msg.put(FORUM_NAME, localState.getString(FORUM_NAME));
-				msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT));
-				if (localAction.containsKey(INVITATION_MSG)) {
-					msg.put(INVITATION_MSG,
-							localAction.getString(INVITATION_MSG));
-				}
+				BaseMessage msg = new Invitation(localState.getGroupId(),
+						localState.getSessionId(), localState.getForumName(),
+						localState.getForumSalt(), localState.getMessage());
 				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
+				logLocalAction(currentState, nextState, msg);
 
 				// remember that we offered to share this forum
-				localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
+				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.getRaw(SESSION_ID));
-				msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
+				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
+						localState.getGroupId(), localState.getSessionId());
 				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
+				logLocalAction(currentState, nextState, msg);
 			}
 			else {
 				throw new IllegalArgumentException("Unknown Local Action");
 			}
-			return new StateUpdate<BdfDictionary, BdfDictionary>(false,
+			return new StateUpdate<SharerSessionState, BaseMessage>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -112,18 +91,16 @@ public class SharerEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
-			BdfDictionary localState, BdfDictionary msg) {
+	public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
+			SharerSessionState localState, BaseMessage msg) {
 
 		try {
-			SharerProtocolState currentState =
-					getState(localState.getLong(STATE));
-			long type = msg.getLong(TYPE);
-			SharerAction action = SharerAction.getRemote(type);
-			SharerProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			State currentState = localState.getState();
+			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) {
@@ -132,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;
 
@@ -141,7 +118,7 @@ public class SharerEngine
 				deleteMsg = true;
 			}
 			else if (action == REMOTE_LEAVE) {
-				localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US);
+				localState.setTask(TASK_UNSHARE_FORUM_SHARED_BY_US);
 			}
 			else if (currentState == FINISHED) {
 				// ignore and delete messages coming in while in that state
@@ -151,74 +128,65 @@ public class SharerEngine
 			// we have sent our invitation and just got a response
 			else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
 				if (action == REMOTE_ACCEPT) {
-					localState.put(TASK, TASK_SHARE_FORUM);
+					localState.setTask(TASK_SHARE_FORUM);
 				} else {
 					// this ensures that the forum can be shared again
-					localState.put(TASK,
+					localState.setTask(
 							TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
 				}
-				String name = localState.getString(FORUM_NAME);
-				ContactId c = new ContactId(
-						localState.getLong(CONTACT_ID).intValue());
+				String name = localState.getForumName();
+				ContactId c = localState.getContactId();
 				Event event = new ForumInvitationResponseReceivedEvent(name, c);
 				events = Collections.singletonList(event);
 			}
 			else {
 				throw new IllegalArgumentException("Bad state");
 			}
-			return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
+			return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
 		}
 	}
 
-	private void logLocalAction(SharerProtocolState state,
-			BdfDictionary localState, BdfDictionary msg) {
+	private void logLocalAction(State currentState, State nextState,
+			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 " + state.name() +
-					" with session ID " +
-					Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
-					Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
-					"Moving on to state " +
-					getState(localState.getLong(STATE)).name()
-			);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
+		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(SharerProtocolState currentState,
-			SharerProtocolState nextState, long type, BdfDictionary msg) {
+	private void logMessageReceived(State currentState, State nextState,
+			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<BdfDictionary, BdfDictionary> onMessageDelivered(
-			BdfDictionary localState, BdfDictionary delivered) {
+	public StateUpdate<SharerSessionState, BaseMessage> onMessageDelivered(
+			SharerSessionState localState, BaseMessage delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -227,38 +195,34 @@ public class SharerEngine
 		}
 	}
 
-	private SharerProtocolState getState(Long state) {
-		 return SharerProtocolState.fromValue(state.intValue());
-	}
-
-	private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
-			SharerProtocolState currentState, BdfDictionary localState)
+	private StateUpdate<SharerSessionState, BaseMessage> abortSession(
+			State currentState, SharerSessionState localState)
 			throws FormatException {
 
 		if (LOG.isLoggable(WARNING)) {
 			LOG.warning("Aborting protocol session " +
-					Arrays.hashCode(localState.getRaw(SESSION_ID)) +
+					localState.getSessionId().hashCode() +
 					" in state " + currentState.name());
 		}
 
-		localState.put(STATE, ERROR.getValue());
-		BdfDictionary msg = new BdfDictionary();
-		msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
-		msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
-		msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
-		List<BdfDictionary> messages = Collections.singletonList(msg);
+		localState.setState(ERROR);
+		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<BdfDictionary, BdfDictionary>(false, false,
+		return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
-			BdfDictionary localState, boolean delete) throws FormatException {
+	private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
+			SharerSessionState localState, boolean delete)
+			throws FormatException {
 
-		return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
-				localState, Collections.<BdfDictionary>emptyList(),
+		return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
+				localState, Collections.<BaseMessage>emptyList(),
 				Collections.<Event>emptyList());
 	}
+
 }
diff --git a/briar-core/src/org/briarproject/forum/SharerSessionState.java b/briar-core/src/org/briarproject/forum/SharerSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..6653648530e7e07a830ec50e5381f75e5a9c6e84
--- /dev/null
+++ b/briar-core/src/org/briarproject/forum/SharerSessionState.java
@@ -0,0 +1,131 @@
+package org.briarproject.forum;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
+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_LEAVE;
+import static org.briarproject.api.forum.ForumConstants.STATE;
+import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
+import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
+import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
+
+// This class is not thread-safe
+public class SharerSessionState extends ForumSharingSessionState {
+
+	private State state;
+	private String msg = null;
+
+	public SharerSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId forumId,
+			String forumName, byte[] forumSalt) {
+
+		super(sessionId, storageId, groupId, contactId, forumId, forumName,
+				forumSalt);
+		this.state = state;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(STATE, getState().getValue());
+		d.put(IS_SHARER, true);
+		return d;
+	}
+
+	public void setState(State state) {
+		this.state = state;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public void setMessage(String msg) {
+		this.msg = msg;
+	}
+
+	public String getMessage() {
+		return this.msg;
+	}
+
+	public enum State {
+		ERROR(0),
+		PREPARE_INVITATION(1) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
+				return ERROR;
+			}
+		},
+		AWAIT_RESPONSE(2) {
+			@Override
+			public State next(Action a) {
+				if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
+				if (a == LOCAL_LEAVE) return LEFT;
+				return ERROR;
+			}
+		},
+		FINISHED(3) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
+				return FINISHED;
+			}
+		},
+		LEFT(4) {
+			@Override
+			public State next(Action a) {
+				if (a == LOCAL_LEAVE) return ERROR;
+				return LEFT;
+			}
+		};
+
+		private final int value;
+
+		State(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public static State fromValue(int value) {
+			for (State s : values()) {
+				if (s.value == value) return s;
+			}
+			throw new IllegalArgumentException();
+		}
+
+		public State next(Action a) {
+			return this;
+		}
+	}
+
+	public enum Action {
+		LOCAL_INVITATION,
+		LOCAL_LEAVE,
+		LOCAL_ABORT,
+		REMOTE_ACCEPT,
+		REMOTE_DECLINE,
+		REMOTE_LEAVE,
+		REMOTE_ABORT;
+
+		public static Action getRemote(long type) {
+			if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
+			if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
+			if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
+			if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
+			return null;
+		}
+	}
+
+}
\ No newline at end of file