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..74e977ac34b505e49438ec2c1491aecf28908b1d 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,7 +55,6 @@ 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;
@@ -76,11 +71,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 +83,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.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.forum.ForumSharingSessionState.fromBdfDictionary;
+import static org.briarproject.forum.SharerSessionState.Action;
 
 class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		implements ForumSharingManager, Client, RemoveForumHook,
@@ -214,10 +205,10 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 					throw new FormatException();
 
 				// initialize state and process invitation
-				BdfDictionary state =
+				InviteeSessionState state =
 						initializeInviteeState(txn, contactId, msg);
 				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);
@@ -226,23 +217,25 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		} else if (type == SHARE_MSG_TYPE_ACCEPT ||
 				type == 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) {
 			// 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,7 +257,8 @@ 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();
@@ -275,7 +269,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 			// start engine and process its state update
 			SharerEngine engine = new SharerEngine();
-			processStateUpdate(txn, null,
+			processSharerStateUpdate(txn, null,
 					engine.onLocalAction(localState, localAction));
 
 			txn.setComplete();
@@ -293,7 +287,7 @@ 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();
@@ -305,7 +299,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 			// start engine and process its state update
 			InviteeEngine engine = new InviteeEngine(forumFactory);
-			processStateUpdate(txn, null,
+			processInviteeStateUpdate(txn, null,
 					engine.onLocalAction(localState, localAction));
 
 			txn.setComplete();
@@ -346,12 +340,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 					boolean available = false;
 					if (!local) {
 						// figure out whether the forum is still available
-						BdfDictionary sessionState =
+						ForumSharingSessionState s =
 								getSessionState(txn, sessionId, true);
-						InviteeProtocolState state = InviteeProtocolState
-								.fromValue(
-										sessionState.getLong(STATE).intValue());
-						available = state == AWAIT_LOCAL_RESPONSE;
+						if (!(s instanceof InviteeSessionState))
+							continue;
+						available = ((InviteeSessionState) s).getState() ==
+								InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
 					}
 					ForumInvitationMessage im =
 							new ForumInvitationMessage(m.getKey(), sessionId,
@@ -490,7 +484,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,26 +496,20 @@ 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,
+	private InviteeSessionState initializeInviteeState(Transaction txn,
 			ContactId contactId, BdfDictionary msg)
 			throws FormatException, DbException {
 
@@ -538,29 +526,25 @@ 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);
+		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());
 
 		// 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 +554,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 +566,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 +605,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,16 +648,16 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<BdfDictionary, BdfDictionary> result)
+			StateUpdate<ForumSharingSessionState, BdfDictionary> 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) {
@@ -677,24 +679,47 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private void performTasks(Transaction txn, BdfDictionary localState)
+	private void processSharerStateUpdate(Transaction txn, MessageId messageId,
+			StateUpdate<SharerSessionState, BdfDictionary> result)
+			throws DbException, FormatException {
+
+		StateUpdate<ForumSharingSessionState, BdfDictionary> r =
+				new StateUpdate<ForumSharingSessionState, BdfDictionary>(
+						result.deleteMessage, result.deleteState,
+						result.localState, result.toSend, result.toBroadcast);
+
+		processStateUpdate(txn, messageId, r);
+	}
+
+	private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
+			StateUpdate<InviteeSessionState, BdfDictionary> result)
+			throws DbException, FormatException {
+
+		StateUpdate<ForumSharingSessionState, BdfDictionary> r =
+				new StateUpdate<ForumSharingSessionState, BdfDictionary>(
+						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
@@ -786,17 +811,17 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	private void leaveForum(Transaction txn, ContactId c, Forum f)
 			throws DbException, FormatException {
 
-		BdfDictionary state = getSessionStateForLeaving(txn, f, c);
+		ForumSharingSessionState state = getSessionStateForLeaving(txn, f, c);
 		BdfDictionary action = new BdfDictionary();
 		action.put(TYPE, SHARE_MSG_TYPE_LEAVE);
-		if (state.getBoolean(IS_SHARER)) {
+		if (state instanceof SharerSessionState) {
 			SharerEngine engine = new SharerEngine();
-			processStateUpdate(txn, null,
-					engine.onLocalAction(state, action));
+			processSharerStateUpdate(txn, null,
+					engine.onLocalAction((SharerSessionState) state, action));
 		} else {
 			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/InviteeEngine.java b/briar-core/src/org/briarproject/forum/InviteeEngine.java
index 70e9a24cb4edaeccf2ae96336d905bbc55d0d139..81d4cf766025797813049005487e0e14072e4b5a 100644
--- a/briar-core/src/org/briarproject/forum/InviteeEngine.java
+++ b/briar-core/src/org/briarproject/forum/InviteeEngine.java
@@ -9,8 +9,6 @@ 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;
@@ -19,9 +17,6 @@ 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;
@@ -29,25 +24,25 @@ 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.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<BdfDictionary, InviteeSessionState, BdfDictionary> {
 
 	private final ForumFactory forumFactory;
 	private static final Logger LOG =
@@ -58,16 +53,15 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
-			BdfDictionary localState, BdfDictionary localAction) {
+	public StateUpdate<InviteeSessionState, BdfDictionary> onLocalAction(
+			InviteeSessionState localState, BdfDictionary localAction) {
 
 		try {
-			InviteeProtocolState currentState =
-					getState(localState.getLong(STATE));
+			State currentState = localState.getState();
 			long type = localAction.getLong(TYPE);
-			InviteeAction action = InviteeAction.getLocal(type);
-			InviteeProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			Action action = Action.getLocal(type);
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			if (action == LOCAL_ABORT && currentState != ERROR) {
 				return abortSession(currentState, localState);
@@ -85,14 +79,14 @@ public class InviteeEngine
 
 			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))
+						new BdfEntry(SESSION_ID, localState.getSessionId()),
+						new BdfEntry(GROUP_ID, localState.getGroupId())
 				);
 				if (action == LOCAL_ACCEPT) {
-					localState.put(TASK, TASK_ADD_SHARED_FORUM);
+					localState.setTask(TASK_ADD_SHARED_FORUM);
 					msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
 				} else {
-					localState.put(TASK,
+					localState.setTask(
 							TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
 					msg.put(TYPE, SHARE_MSG_TYPE_DECLINE);
 				}
@@ -102,15 +96,15 @@ public class InviteeEngine
 			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));
+				msg.put(SESSION_ID, localState.getSessionId());
+				msg.put(GROUP_ID, localState.getGroupId());
 				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, BdfDictionary>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -118,16 +112,15 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
-			BdfDictionary localState, BdfDictionary msg) {
+	public StateUpdate<InviteeSessionState, BdfDictionary> onMessageReceived(
+			InviteeSessionState localState, BdfDictionary msg) {
 
 		try {
-			InviteeProtocolState currentState =
-					getState(localState.getLong(STATE));
+			State currentState = localState.getState();
 			long type = msg.getLong(TYPE);
-			InviteeAction action = InviteeAction.getRemote(type);
-			InviteeProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			Action action = Action.getRemote(type);
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			logMessageReceived(currentState, nextState, type, msg);
 
@@ -149,7 +142,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,31 +151,30 @@ 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, BdfDictionary>(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, BdfDictionary msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
@@ -194,16 +186,16 @@ public class InviteeEngine
 					" 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()
+					"Moving on to state " + localState.getState().name()
 			);
 		} catch (FormatException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
 	}
 
-	private void logMessageReceived(InviteeProtocolState currentState,
-			InviteeProtocolState nextState, long type, BdfDictionary msg) {
+	private void logMessageReceived(State currentState, State nextState,
+			long type, BdfDictionary msg) {
+
 		if (!LOG.isLoggable(INFO)) return;
 
 		try {
@@ -224,8 +216,8 @@ public class InviteeEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
-			BdfDictionary localState, BdfDictionary delivered) {
+	public StateUpdate<InviteeSessionState, BdfDictionary> onMessageDelivered(
+			InviteeSessionState localState, BdfDictionary delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -234,37 +226,33 @@ 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, BdfDictionary> 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());
+		localState.setState(ERROR);
 		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));
+		msg.put(SESSION_ID, localState.getSessionId());
+		msg.put(GROUP_ID, localState.getGroupId());
 		List<BdfDictionary> messages = Collections.singletonList(msg);
 
 		List<Event> events = Collections.emptyList();
 
-		return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
+		return new StateUpdate<InviteeSessionState, BdfDictionary>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
-			BdfDictionary localState, boolean delete) throws FormatException {
+	private StateUpdate<InviteeSessionState, BdfDictionary> noUpdate(
+			InviteeSessionState localState, boolean delete) throws FormatException {
 
-		return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
+		return new StateUpdate<InviteeSessionState, BdfDictionary>(delete, false,
 				localState, Collections.<BdfDictionary>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..cc4e1c40401b6970add1231e57be46f4067188af
--- /dev/null
+++ b/briar-core/src/org/briarproject/forum/InviteeSessionState.java
@@ -0,0 +1,130 @@
+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_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 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 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..3e81470ae4812b2956bf31cbb8d91072023ae179 100644
--- a/briar-core/src/org/briarproject/forum/SharerEngine.java
+++ b/briar-core/src/org/briarproject/forum/SharerEngine.java
@@ -6,8 +6,7 @@ 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 static org.briarproject.forum.SharerSessionState.Action;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -16,7 +15,6 @@ 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;
@@ -27,40 +25,38 @@ 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.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<BdfDictionary, SharerSessionState, BdfDictionary> {
 
 	private static final Logger LOG =
 			Logger.getLogger(SharerEngine.class.getName());
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
-			BdfDictionary localState, BdfDictionary localAction) {
+	public StateUpdate<SharerSessionState, BdfDictionary> onLocalAction(
+			SharerSessionState localState, BdfDictionary localAction) {
 
 		try {
-			SharerProtocolState currentState =
-					getState(localState.getLong(STATE));
+			State currentState = localState.getState();
 			long type = localAction.getLong(TYPE);
-			SharerAction action = SharerAction.getLocal(type);
-			SharerProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			Action action = Action.getLocal(type);
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			if (action == LOCAL_ABORT && currentState != ERROR) {
 				return abortSession(currentState, localState);
@@ -79,32 +75,32 @@ public class SharerEngine
 			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));
+				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 (localAction.containsKey(INVITATION_MSG)) {
 					msg.put(INVITATION_MSG,
 							localAction.getString(INVITATION_MSG));
 				}
 				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));
+				msg.put(SESSION_ID, localState.getSessionId());
+				msg.put(GROUP_ID, localState.getGroupId());
 				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, BdfDictionary>(false,
 					false, localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
@@ -112,16 +108,15 @@ public class SharerEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
-			BdfDictionary localState, BdfDictionary msg) {
+	public StateUpdate<SharerSessionState, BdfDictionary> onMessageReceived(
+			SharerSessionState localState, BdfDictionary msg) {
 
 		try {
-			SharerProtocolState currentState =
-					getState(localState.getLong(STATE));
+			State currentState = localState.getState();
 			long type = msg.getLong(TYPE);
-			SharerAction action = SharerAction.getRemote(type);
-			SharerProtocolState nextState = currentState.next(action);
-			localState.put(STATE, nextState.getValue());
+			Action action = Action.getRemote(type);
+			State nextState = currentState.next(action);
+			localState.setState(nextState);
 
 			logMessageReceived(currentState, nextState, type, msg);
 
@@ -141,7 +136,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,30 +146,29 @@ 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, BdfDictionary>(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,
+			BdfDictionary msg) {
 
 		if (!LOG.isLoggable(INFO)) return;
 
@@ -182,20 +176,20 @@ public class SharerEngine
 		if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
 
 		try {
-			LOG.info("Sending " + a + " in state " + state.name() +
+			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 " +
-					getState(localState.getLong(STATE)).name()
+					"Moving on to state " + nextState.name()
 			);
 		} catch (FormatException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
 	}
 
-	private void logMessageReceived(SharerProtocolState currentState,
-			SharerProtocolState nextState, long type, BdfDictionary msg) {
+	private void logMessageReceived(State currentState, State nextState,
+			long type, BdfDictionary msg) {
+
 		if (!LOG.isLoggable(INFO)) return;
 
 		try {
@@ -217,8 +211,8 @@ public class SharerEngine
 	}
 
 	@Override
-	public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
-			BdfDictionary localState, BdfDictionary delivered) {
+	public StateUpdate<SharerSessionState, BdfDictionary> onMessageDelivered(
+			SharerSessionState localState, BdfDictionary delivered) {
 		try {
 			return noUpdate(localState, false);
 		} catch (FormatException e) {
@@ -227,38 +221,36 @@ 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, BdfDictionary> 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());
+		localState.setState(ERROR);
 		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));
+		msg.put(SESSION_ID, localState.getSessionId());
+		msg.put(GROUP_ID, localState.getGroupId());
 		List<BdfDictionary> messages = Collections.singletonList(msg);
 
 		List<Event> events = Collections.emptyList();
 
-		return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
+		return new StateUpdate<SharerSessionState, BdfDictionary>(false, false,
 				localState, messages, events);
 	}
 
-	private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
-			BdfDictionary localState, boolean delete) throws FormatException {
+	private StateUpdate<SharerSessionState, BdfDictionary> noUpdate(
+			SharerSessionState localState, boolean delete)
+			throws FormatException {
 
-		return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
+		return new StateUpdate<SharerSessionState, BdfDictionary>(delete, false,
 				localState, Collections.<BdfDictionary>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..b438c673459d05ef7ebd602ff4a44fc924742c69
--- /dev/null
+++ b/briar-core/src/org/briarproject/forum/SharerSessionState.java
@@ -0,0 +1,130 @@
+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_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.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;
+
+	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 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 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 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