diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
index 28df61f7451d8f74267d610005ca110503579aa5..99c4514b2639e51005df4459ad00d9ae28c11d86 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
@@ -1,5 +1,6 @@
 package org.briarproject.briar.api.blog;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 public interface BlogConstants {
@@ -7,12 +8,7 @@ public interface BlogConstants {
 	/**
 	 * The maximum length of a blogs's name in UTF-8 bytes.
 	 */
-	int MAX_BLOG_TITLE_LENGTH = 100;
-
-	/**
-	 * The length of a blogs's description in UTF-8 bytes.
-	 */
-	int MAX_BLOG_DESC_LENGTH = 240;
+	int MAX_BLOG_NAME_LENGTH = MAX_AUTHOR_NAME_LENGTH;
 
 	/**
 	 * The maximum length of a blog post's body in bytes.
@@ -24,13 +20,8 @@ public interface BlogConstants {
 	 */
 	int MAX_BLOG_COMMENT_LENGTH = MAX_BLOG_POST_BODY_LENGTH;
 
-	/* Blog Sharing Constants */
-	String BLOG_AUTHOR_NAME = "blogAuthorName";
-	String BLOG_PUBLIC_KEY = "blogPublicKey";
-
 	// Metadata keys
 	String KEY_TYPE = "type";
-	String KEY_DESCRIPTION = "description";
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_TIME_RECEIVED = "timeReceived";
 	String KEY_AUTHOR_ID = "id";
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationRequest.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationRequest.java
index 0618f7b088b7c45c96fff92642d505f5e79c4706..77dddbbe034303beb720f3f1475b4c254d3b85d6 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationRequest.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationRequest.java
@@ -1,10 +1,7 @@
 package org.briarproject.briar.api.blog;
 
 import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.SessionId;
@@ -15,24 +12,16 @@ import javax.annotation.Nullable;
 @NotNullByDefault
 public class BlogInvitationRequest extends InvitationRequest<Blog> {
 
-	private final String blogAuthorName;
-
-	public BlogInvitationRequest(MessageId id, SessionId sessionId,
-			GroupId groupId, ContactId contactId, String blogAuthorName,
-			@Nullable String message, GroupId blogId,
-			boolean available, boolean canBeOpened, long time,
-			boolean local, boolean sent, boolean seen, boolean read) {
-		// TODO pass a proper blog here when redoing the BlogSharingManager
-		super(id, groupId, time, local, sent, seen, read, sessionId,
-				new Blog(new Group(blogId, BlogManager.CLIENT_ID, new byte[0]),
-						new Author(new AuthorId(new byte[AuthorId.LENGTH]),
-								blogAuthorName, new byte[0])), contactId,
-				message, available, canBeOpened);
-		this.blogAuthorName = blogAuthorName;
+	public BlogInvitationRequest(MessageId id, GroupId groupId, long time,
+			boolean local, boolean sent, boolean seen, boolean read,
+			SessionId sessionId, Blog blog, ContactId contactId,
+			@Nullable String message, boolean available, boolean canBeOpened) {
+		super(id, groupId, time, local, sent, seen, read, sessionId, blog,
+				contactId, message, available, canBeOpened);
 	}
 
 	public String getBlogAuthorName() {
-		return blogAuthorName;
+		return getShareable().getName();
 	}
 
 }
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationResponse.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationResponse.java
index 32eaa44405fd5f69aacaa7d2db6f2a4e342d7276..e4914230020f7543e3a9709c41f698068253c13a 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationResponse.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogInvitationResponse.java
@@ -10,10 +10,10 @@ import org.briarproject.briar.api.sharing.InvitationResponse;
 @NotNullByDefault
 public class BlogInvitationResponse extends InvitationResponse {
 
-	public BlogInvitationResponse(MessageId id, SessionId sessionId,
-			GroupId groupId, ContactId contactId, GroupId blogId,
-			boolean accept, long time, boolean local, boolean sent,
-			boolean seen, boolean read) {
+	public BlogInvitationResponse(MessageId id, GroupId groupId, long time,
+			boolean local, boolean sent, boolean seen, boolean read,
+			SessionId sessionId, GroupId blogId, ContactId contactId,
+			boolean accept) {
 		super(id, groupId, time, local, sent, seen, read, sessionId, blogId,
 				contactId, accept);
 	}
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
index 8b596d6b65128599d494037ece1c1e30cd787dc4..20ffb991cac62a97aa50182af88299e2b5ff08fd 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
@@ -21,6 +21,16 @@ public interface BlogManager {
 	 */
 	ClientId CLIENT_ID = new ClientId("org.briarproject.briar.blog");
 
+	/**
+	 * Adds a blog from the given author.
+	 */
+	Blog addBlog(Author author) throws DbException;
+
+	/**
+	 * Adds the given {@link Blog} within the given {@link Transaction}.
+	 */
+	void addBlog(Transaction txn, Blog b) throws DbException;
+
 	/**
 	 * Returns true if a blog can be removed.
 	 */
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingMessage.java
deleted file mode 100644
index 811bf32e850bf339465f2c6775c1d334feca040f..0000000000000000000000000000000000000000
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogSharingMessage.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.briarproject.briar.api.blog;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.SharingMessage.Invitation;
-
-import javax.annotation.Nullable;
-
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_AUTHOR_NAME;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_PUBLIC_KEY;
-import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
-
-@NotNullByDefault
-public interface BlogSharingMessage {
-
-	class BlogInvitation extends Invitation {
-
-		private final String blogAuthorName;
-		private final byte[] blogPublicKey;
-
-		public BlogInvitation(GroupId groupId, SessionId sessionId,
-				String blogAuthorName, byte[] blogPublicKey, long time,
-				@Nullable String message) {
-			super(groupId, sessionId, time, message);
-
-			this.blogAuthorName = blogAuthorName;
-			this.blogPublicKey = blogPublicKey;
-		}
-
-		@Override
-		public BdfList toBdfList() {
-			BdfList list = super.toBdfList();
-			list.add(BdfList.of(blogAuthorName, blogPublicKey));
-			if (message != null) list.add(message);
-			return list;
-		}
-
-		@Override
-		public BdfDictionary toBdfDictionary() {
-			BdfDictionary d = toBdfDictionaryHelper();
-			d.put(BLOG_AUTHOR_NAME, blogAuthorName);
-			d.put(BLOG_PUBLIC_KEY, blogPublicKey);
-			if (message != null) d.put(INVITATION_MSG, message);
-			return d;
-		}
-
-		public static BlogInvitation from(GroupId groupId, BdfDictionary d)
-				throws FormatException {
-
-			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
-			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
-			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
-			String message = d.getOptionalString(INVITATION_MSG);
-			long time = d.getLong(TIME);
-
-			return new BlogInvitation(groupId, sessionId, blogAuthorName,
-					blogPublicKey, time, message);
-		}
-
-		public String getBlogAuthorName() {
-			return blogAuthorName;
-		}
-
-		public byte[] getBlogPublicKey() {
-			return blogPublicKey;
-		}
-	}
-}
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingMessage.java
deleted file mode 100644
index c8a08752e5652af0acb3eeec3de1f1ab98ab79a8..0000000000000000000000000000000000000000
--- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumSharingMessage.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.briarproject.briar.api.forum;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.SharingMessage.Invitation;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
-import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
-import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
-
-@NotNullByDefault
-public interface ForumSharingMessage {
-
-	@Immutable
-	@NotNullByDefault
-	class ForumInvitation extends Invitation {
-
-		private final String forumName;
-		private final byte[] forumSalt;
-
-		public ForumInvitation(GroupId groupId, SessionId sessionId,
-				String forumName, byte[] forumSalt, long time,
-				@Nullable String message) {
-
-			super(groupId, sessionId, time, message);
-
-			this.forumName = forumName;
-			this.forumSalt = forumSalt;
-		}
-
-		@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 ForumInvitation 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);
-			long time = d.getLong(TIME);
-
-			return new ForumInvitation(groupId, sessionId, forumName, forumSalt,
-					time, message);
-		}
-
-		public String getForumName() {
-			return forumName;
-		}
-
-		public byte[] getForumSalt() {
-			return forumSalt;
-		}
-	}
-}
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java
deleted file mode 100644
index 5b4e275a3958588e71a66d725f06aef9474ac332..0000000000000000000000000000000000000000
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/InvitationFactory.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.briarproject.briar.api.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.sync.GroupId;
-
-@Deprecated
-public interface InvitationFactory<I extends SharingMessage.Invitation> {
-
-	I build(GroupId groupId, BdfDictionary d) throws FormatException;
-}
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
index 6b8fcc4a058a1fcbb1b6708a51a3ec97f108d617..81eb45aff61d8f51abd6adcb7f0e515a9fcafc3a 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingConstants.java
@@ -4,74 +4,10 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L
 
 public interface SharingConstants {
 
-	/**
-	 * The length of a sharing session's random salt in bytes.
-	 */
-	int SHARING_SALT_LENGTH = 32;
-
 	/**
 	 * The maximum length of the optional message from the inviter to the
 	 * invitee in UTF-8 bytes.
 	 */
 	int MAX_INVITATION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
-	@Deprecated
-	String CONTACT_ID = "contactId";
-	@Deprecated
-	String GROUP_ID = "groupId";
-	@Deprecated
-	String TO_BE_SHARED_BY_US = "toBeSharedByUs";
-	@Deprecated
-	String SHARED_BY_US = "sharedByUs";
-	@Deprecated
-	String SHARED_WITH_US = "sharedWithUs";
-	@Deprecated
-	String TYPE = "type";
-	@Deprecated
-	String SESSION_ID = "sessionId";
-	@Deprecated
-	String STORAGE_ID = "storageId";
-	@Deprecated
-	String STATE = "state";
-	@Deprecated
-	String LOCAL = "local";
-	@Deprecated
-	String TIME = "time";
-	@Deprecated
-	String IS_SHARER = "isSharer";
-	@Deprecated
-	String SHAREABLE_ID = "shareableId";
-	@Deprecated
-	String INVITATION_MSG = "invitationMsg";
-	@Deprecated
-	String INVITATION_ID = "invitationId";
-	@Deprecated
-	String RESPONSE_ID = "responseId";
-	@Deprecated
-	int SHARE_MSG_TYPE_INVITATION = 1;
-	@Deprecated
-	int SHARE_MSG_TYPE_ACCEPT = 2;
-	@Deprecated
-	int SHARE_MSG_TYPE_DECLINE = 3;
-	@Deprecated
-	int SHARE_MSG_TYPE_LEAVE = 4;
-	@Deprecated
-	int SHARE_MSG_TYPE_ABORT = 5;
-	@Deprecated
-	int TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US = 0;
-	@Deprecated
-	int TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US = 1;
-	@Deprecated
-	int TASK_ADD_SHARED_SHAREABLE = 2;
-	@Deprecated
-	int TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US = 3;
-	@Deprecated
-	int TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US = 4;
-	@Deprecated
-	int TASK_SHARE_SHAREABLE = 5;
-	@Deprecated
-	int TASK_UNSHARE_SHAREABLE_SHARED_BY_US = 6;
-	@Deprecated
-	int TASK_UNSHARE_SHAREABLE_SHARED_WITH_US = 7;
-
 }
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java
deleted file mode 100644
index dd4b987aa95d6b3283632c147b3980f350bf00d3..0000000000000000000000000000000000000000
--- a/briar-api/src/main/java/org/briarproject/briar/api/sharing/SharingMessage.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.briarproject.briar.api.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfEntry;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import static org.briarproject.briar.api.sharing.SharingConstants.GROUP_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
-import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
-
-@Deprecated
-@NotNullByDefault
-public interface SharingMessage {
-
-	@Immutable
-	@NotNullByDefault
-	abstract class BaseMessage {
-
-		private final GroupId groupId;
-		private final SessionId sessionId;
-		private final long time;
-
-		BaseMessage(GroupId groupId, SessionId sessionId, long time) {
-			this.groupId = groupId;
-			this.sessionId = sessionId;
-			this.time = time;
-		}
-
-		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(InvitationFactory invitationFactory,
-				GroupId groupId, BdfDictionary d)
-				throws FormatException {
-
-			long type = d.getLong(TYPE);
-
-			if (type == SHARE_MSG_TYPE_INVITATION)
-				return invitationFactory.build(groupId, d);
-			else
-				return SimpleMessage.from(type, groupId, d);
-		}
-
-		public abstract long getType();
-
-		public GroupId getGroupId() {
-			return groupId;
-		}
-
-		public SessionId getSessionId() {
-			return sessionId;
-		}
-
-		public long getTime() {
-			return time;
-		}
-	}
-
-	@Immutable
-	@NotNullByDefault
-	abstract class Invitation extends BaseMessage {
-
-		@Nullable
-		protected final String message;
-
-		public Invitation(GroupId groupId, SessionId sessionId, long time,
-				@Nullable String message) {
-
-			super(groupId, sessionId, time);
-
-			this.message = message;
-		}
-
-		@Override
-		public long getType() {
-			return SHARE_MSG_TYPE_INVITATION;
-		}
-
-		@Nullable
-		public String getMessage() {
-			return message;
-		}
-	}
-
-	@Immutable
-	@NotNullByDefault
-	class SimpleMessage extends BaseMessage {
-
-		private final long type;
-
-		public SimpleMessage(long type, GroupId groupId, SessionId sessionId,
-				long time) {
-			super(groupId, sessionId, time);
-			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));
-			long time = d.getLong(TIME);
-			return new SimpleMessage(type, groupId, sessionId, time);
-		}
-	}
-
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
index deab371d7948475bff8492596863ac849f924656..2178d423267061b97151ea1a1ecda44cd1c74a4c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
@@ -108,7 +108,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	public void addingContact(Transaction txn, Contact c) throws DbException {
 		// Add the personal blog of the contact and share it with the contact
 		Blog b = blogFactory.createBlog(c.getAuthor());
-		db.addGroup(txn, b.getGroup());
+		addBlog(txn, b);
 		db.setGroupVisibility(txn, c.getId(), b.getId(), SHARED);
 		// Share our personal blog with the contact
 		LocalAuthor a = identityManager.getLocalAuthor(txn);
@@ -170,6 +170,25 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		return false;
 	}
 
+	@Override
+	public Blog addBlog(Author author) throws DbException {
+		Blog b = blogFactory.createBlog(author);
+
+		Transaction txn = db.startTransaction(false);
+		try {
+			db.addGroup(txn, b.getGroup());
+			db.commitTransaction(txn);
+		} finally {
+			db.endTransaction(txn);
+		}
+		return b;
+	}
+
+	@Override
+	public void addBlog(Transaction txn, Blog b) throws DbException {
+		db.addGroup(txn, b.getGroup());
+	}
+
 	@Override
 	public boolean canBeRemoved(GroupId g) throws DbException {
 		Transaction txn = db.startTransaction(true);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInvitationFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInvitationFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..606d6fa99d7ba7ebc84d0c18d35a2e77133397d9
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInvitationFactoryImpl.java
@@ -0,0 +1,39 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogInvitationRequest;
+import org.briarproject.briar.api.blog.BlogInvitationResponse;
+import org.briarproject.briar.api.client.SessionId;
+
+import javax.inject.Inject;
+
+public class BlogInvitationFactoryImpl implements InvitationFactory<Blog> {
+
+	@Inject
+	BlogInvitationFactoryImpl() {
+	}
+
+	@Override
+	public BlogInvitationRequest createInvitationRequest(boolean local,
+			boolean sent, boolean seen, boolean read, InviteMessage<Blog> m,
+			ContactId c, boolean available, boolean canBeOpened) {
+		SessionId sessionId = new SessionId(m.getShareableId().getBytes());
+		return new BlogInvitationRequest(m.getId(), m.getContactGroupId(),
+				m.getTimestamp(), local, sent, seen, read, sessionId,
+				m.getShareable(), c, m.getMessage(), available, canBeOpened);
+	}
+
+	@Override
+	public BlogInvitationResponse createInvitationResponse(MessageId id,
+			GroupId contactGroupId, long time, boolean local, boolean sent,
+			boolean seen, boolean read, GroupId shareableId,
+			ContactId contactId, boolean accept) {
+		SessionId sessionId = new SessionId(shareableId.getBytes());
+		return new BlogInvitationResponse(id, contactGroupId, time, local,
+				sent, seen, read, sessionId, shareableId, contactId, accept);
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInviteeSessionState.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInviteeSessionState.java
deleted file mode 100644
index 564eec6cc7be4dc7122ca6b1b2864a6bd7ca9b56..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogInviteeSessionState.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.concurrent.NotThreadSafe;
-
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_AUTHOR_NAME;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_PUBLIC_KEY;
-
-@NotThreadSafe
-@NotNullByDefault
-class BlogInviteeSessionState extends InviteeSessionState {
-
-	private final String blogAuthorName;
-	private final byte[] blogPublicKey;
-
-	BlogInviteeSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId, GroupId blogId,
-			String blogAuthorName, byte[] blogPublicKey,
-			MessageId invitationId) {
-		super(sessionId, storageId, groupId, state, contactId, blogId,
-				invitationId);
-		this.blogAuthorName = blogAuthorName;
-		this.blogPublicKey = blogPublicKey;
-	}
-
-	@Override
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = super.toBdfDictionary();
-		d.put(BLOG_AUTHOR_NAME, getBlogAuthorName());
-		d.put(BLOG_PUBLIC_KEY, getBlogPublicKey());
-		return d;
-	}
-
-	String getBlogAuthorName() {
-		return blogAuthorName;
-	}
-
-	byte[] getBlogPublicKey() {
-		return blogPublicKey;
-	}
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..bcf0115191336a50bec271cef6eb9f61695e58f1
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java
@@ -0,0 +1,39 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogFactory;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+@Immutable
+@NotNullByDefault
+class BlogMessageParserImpl extends MessageParserImpl<Blog> {
+
+	private final BlogFactory blogFactory;
+	private final AuthorFactory authorFactory;
+
+	@Inject
+	BlogMessageParserImpl(ClientHelper clientHelper, BlogFactory blogFactory,
+			AuthorFactory authorFactory) {
+		super(clientHelper);
+		this.blogFactory = blogFactory;
+		this.authorFactory = authorFactory;
+	}
+
+	@Override
+	protected Blog createShareable(BdfList descriptor)
+			throws FormatException {
+		String name = descriptor.getString(0);
+		byte[] publicKey = descriptor.getRaw(1);
+		Author author = authorFactory.createAuthor(name, publicKey);
+		return blogFactory.createBlog(author);
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..79ba79d4a94491bd1207e6a7593346f104943ae0
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogProtocolEngineImpl.java
@@ -0,0 +1,93 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.db.DatabaseComponent;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.event.Event;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.sync.ClientId;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogInvitationRequest;
+import org.briarproject.briar.api.blog.BlogInvitationResponse;
+import org.briarproject.briar.api.blog.BlogManager;
+import org.briarproject.briar.api.blog.BlogSharingManager;
+import org.briarproject.briar.api.blog.event.BlogInvitationRequestReceivedEvent;
+import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
+import org.briarproject.briar.api.client.MessageTracker;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+@Immutable
+@NotNullByDefault
+class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> {
+
+	private final BlogManager blogManager;
+	private final InvitationFactory<Blog> invitationFactory;
+
+	@Inject
+	BlogProtocolEngineImpl(DatabaseComponent db,
+			ClientHelper clientHelper, MessageEncoder messageEncoder,
+			MessageParser<Blog> messageParser, MessageTracker messageTracker,
+			Clock clock, BlogManager blogManager,
+			InvitationFactory<Blog> invitationFactory) {
+		super(db, clientHelper, messageEncoder, messageParser, messageTracker,
+				clock);
+		this.blogManager = blogManager;
+		this.invitationFactory = invitationFactory;
+	}
+
+	@Override
+	Event getInvitationRequestReceivedEvent(InviteMessage<Blog> m,
+			ContactId contactId, boolean available, boolean canBeOpened) {
+		BlogInvitationRequest request =
+				(BlogInvitationRequest) invitationFactory
+						.createInvitationRequest(false, false, true, false, m,
+								contactId, available, canBeOpened);
+		return new BlogInvitationRequestReceivedEvent(m.getShareable(),
+				contactId, request);
+	}
+
+	@Override
+	Event getInvitationResponseReceivedEvent(AcceptMessage m,
+			ContactId contactId) {
+		BlogInvitationResponse response =
+				(BlogInvitationResponse) invitationFactory
+						.createInvitationResponse(m.getId(),
+								m.getContactGroupId(), m.getTimestamp(), false,
+								false, true, false, m.getShareableId(),
+								contactId, true);
+		return new BlogInvitationResponseReceivedEvent(contactId, response);
+	}
+
+	@Override
+	Event getInvitationResponseReceivedEvent(DeclineMessage m,
+			ContactId contactId) {
+		BlogInvitationResponse response =
+				(BlogInvitationResponse) invitationFactory
+						.createInvitationResponse(m.getId(),
+								m.getContactGroupId(), m.getTimestamp(), false,
+								false, true, false, m.getShareableId(),
+								contactId, true);
+		return new BlogInvitationResponseReceivedEvent(contactId, response);
+	}
+
+	@Override
+	protected ClientId getClientId() {
+		return BlogSharingManager.CLIENT_ID;
+	}
+
+	@Override
+	protected void addShareable(Transaction txn, MessageId inviteId)
+			throws DbException, FormatException {
+		InviteMessage<Blog> invite =
+				messageParser.getInviteMessage(txn, inviteId);
+		blogManager.addBlog(txn, invite.getShareable());
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharerSessionState.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharerSessionState.java
deleted file mode 100644
index bc4b34b5e5d3a22d726fea772aa7a5dc789b06bc..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharerSessionState.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.NotThreadSafe;
-
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_AUTHOR_NAME;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_PUBLIC_KEY;
-
-@NotThreadSafe
-@NotNullByDefault
-class BlogSharerSessionState extends SharerSessionState {
-
-	private final String blogAuthorName;
-	private final byte[] blogPublicKey;
-
-	BlogSharerSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId, GroupId blogId,
-			String blogAuthorName, byte[] blogPublicKey,
-			@Nullable MessageId responseId) {
-		super(sessionId, storageId, groupId, state, contactId, blogId,
-				responseId);
-
-		this.blogAuthorName = blogAuthorName;
-		this.blogPublicKey = blogPublicKey;
-	}
-
-	@Override
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = super.toBdfDictionary();
-		d.put(BLOG_AUTHOR_NAME, getBlogAuthorName());
-		d.put(BLOG_PUBLIC_KEY, getBlogPublicKey());
-		return d;
-	}
-
-	String getBlogAuthorName() {
-		return blogAuthorName;
-	}
-
-	byte[] getBlogPublicKey() {
-		return blogPublicKey;
-	}
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
index a436dd9a39bb7c3ae826fb21a3b3f52b3fd68be7..9133b49174575aaf37e18bc136bd09b931ddd080 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingManagerImpl.java
@@ -1,93 +1,48 @@
 package org.briarproject.briar.sharing;
 
-import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.client.ContactGroupFactory;
 import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.contact.ContactManager;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.data.MetadataParser;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.ClientId;
 import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.blog.Blog;
-import org.briarproject.briar.api.blog.BlogFactory;
-import org.briarproject.briar.api.blog.BlogInvitationRequest;
-import org.briarproject.briar.api.blog.BlogInvitationResponse;
 import org.briarproject.briar.api.blog.BlogManager;
 import org.briarproject.briar.api.blog.BlogManager.RemoveBlogHook;
 import org.briarproject.briar.api.blog.BlogSharingManager;
-import org.briarproject.briar.api.blog.BlogSharingMessage.BlogInvitation;
-import org.briarproject.briar.api.blog.event.BlogInvitationRequestReceivedEvent;
-import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
-import org.briarproject.briar.api.client.MessageQueueManager;
 import org.briarproject.briar.api.client.MessageTracker;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.InvitationMessage;
 
-import java.security.SecureRandom;
-import java.util.Collection;
-
-import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_AUTHOR_NAME;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_PUBLIC_KEY;
-import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.RESPONSE_ID;
-
 @Immutable
 @NotNullByDefault
-class BlogSharingManagerImpl extends
-		OldSharingManagerImpl<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationRequestReceivedEvent, BlogInvitationResponseReceivedEvent>
+class BlogSharingManagerImpl extends SharingManagerImpl<Blog>
 		implements BlogSharingManager, RemoveBlogHook {
 
-	private final ContactManager contactManager;
 	private final IdentityManager identityManager;
 	private final BlogManager blogManager;
 
-	private final SFactory sFactory;
-	private final IFactory iFactory;
-	private final ISFactory isFactory;
-	private final SSFactory ssFactory;
-	private final IRFactory irFactory;
-	private final IRRFactory irrFactory;
-
 	@Inject
-	BlogSharingManagerImpl(AuthorFactory authorFactory, BlogFactory blogFactory,
-			BlogManager blogManager, ClientHelper clientHelper, Clock clock,
-			DatabaseComponent db, MessageQueueManager messageQueueManager,
-			MetadataEncoder metadataEncoder, MetadataParser metadataParser,
-			ContactGroupFactory contactGroupFactory, SecureRandom random,
-			ContactManager contactManager, IdentityManager identityManager,
-			MessageTracker messageTracker) {
-
-		super(db, messageQueueManager, clientHelper, metadataParser,
-				metadataEncoder, random, contactGroupFactory, messageTracker,
-				clock);
-
-		this.blogManager = blogManager;
-		this.contactManager = contactManager;
+	BlogSharingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
+			MetadataParser metadataParser, MessageParser<Blog> messageParser,
+			SessionEncoder sessionEncoder, SessionParser sessionParser,
+			MessageTracker messageTracker,
+			ContactGroupFactory contactGroupFactory,
+			ProtocolEngine<Blog> engine,
+			InvitationFactory<Blog> invitationFactory,
+			IdentityManager identityManager, BlogManager blogManager) {
+		super(db, clientHelper, metadataParser, messageParser, sessionEncoder,
+				sessionParser, messageTracker, contactGroupFactory, engine,
+				invitationFactory);
 		this.identityManager = identityManager;
-		sFactory = new SFactory(authorFactory, blogFactory, blogManager);
-		iFactory = new IFactory();
-		isFactory = new ISFactory();
-		ssFactory = new SSFactory();
-		irFactory = new IRFactory(sFactory);
-		irrFactory = new IRRFactory();
+		this.blogManager = blogManager;
 	}
 
 	@Override
@@ -96,91 +51,18 @@ class BlogSharingManagerImpl extends
 	}
 
 	@Override
-	protected boolean canBeShared(Transaction txn, GroupId g, Contact c)
-			throws DbException {
-
-		// check if g is our personal blog
+	protected boolean canBeShared(Transaction txn, GroupId shareableId,
+			Contact c) throws DbException {
+		// check if shareableId belongs to our personal blog
 		LocalAuthor author = identityManager.getLocalAuthor(txn);
 		Blog b = blogManager.getPersonalBlog(author);
-		if (b.getId().equals(g)) return false;
+		if (b.getId().equals(shareableId)) return false;
 
-		// check if g is c's personal blog
+		// check if shareableId belongs to c's personal blog
 		b = blogManager.getPersonalBlog(c.getAuthor());
-		if (b.getId().equals(g)) return false;
+		if (b.getId().equals(shareableId)) return false;
 
-		return super.canBeShared(txn, g, c);
-	}
-
-	@Override
-	public Collection<Contact> getSharedWith(GroupId g) throws DbException {
-		Blog blog = blogManager.getBlog(g);
-		LocalAuthor author = identityManager.getLocalAuthor();
-		if (blog.getAuthor().equals(author)) {
-			// This is our personal blog. It is shared with all our contacts
-			return contactManager.getActiveContacts();
-		} else {
-			// This is someone else's blog. Look up who it is shared with
-			Collection<Contact> shared = super.getSharedWith(g);
-			// If the blog author is our contact, also add her to the list
-			boolean isContact = contactManager
-					.contactExists(blog.getAuthor().getId(), author.getId());
-			if (isContact) {
-				shared.add(contactManager
-						.getContact(blog.getAuthor().getId(), author.getId()));
-			}
-			return shared;
-		}
-	}
-
-	@Override
-	protected InvitationMessage createInvitationRequest(MessageId id,
-			BlogInvitation msg, ContactId contactId, GroupId blogId,
-			boolean available, boolean canBeOpened, long time, boolean local,
-			boolean sent, boolean seen, boolean read) {
-
-		return new BlogInvitationRequest(id, msg.getSessionId(),
-				msg.getGroupId(), contactId, msg.getBlogAuthorName(),
-				msg.getMessage(), blogId, available, canBeOpened, time, local,
-				sent, seen, read);
-	}
-
-	@Override
-	protected InvitationMessage createInvitationResponse(MessageId id,
-			SessionId sessionId, GroupId groupId, ContactId contactId,
-			GroupId blogId, boolean accept, long time, boolean local,
-			boolean sent, boolean seen, boolean read) {
-		return new BlogInvitationResponse(id, sessionId, groupId, contactId,
-				blogId, accept, time, local, sent, seen, read);
-	}
-
-	@Override
-	protected ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> getSFactory() {
-		return sFactory;
-	}
-
-	@Override
-	protected OldInvitationFactory<BlogInvitation, BlogSharerSessionState> getIFactory() {
-		return iFactory;
-	}
-
-	@Override
-	protected InviteeSessionStateFactory<Blog, BlogInviteeSessionState> getISFactory() {
-		return isFactory;
-	}
-
-	@Override
-	protected SharerSessionStateFactory<Blog, BlogSharerSessionState> getSSFactory() {
-		return ssFactory;
-	}
-
-	@Override
-	protected InvitationReceivedEventFactory<BlogInviteeSessionState, BlogInvitationRequestReceivedEvent> getIRFactory() {
-		return irFactory;
-	}
-
-	@Override
-	protected InvitationResponseReceivedEventFactory<BlogSharerSessionState, BlogInvitationResponseReceivedEvent> getIRRFactory() {
-		return irrFactory;
+		return super.canBeShared(txn, shareableId, c);
 	}
 
 	@Override
@@ -188,187 +70,4 @@ class BlogSharingManagerImpl extends
 		removingShareable(txn, b);
 	}
 
-	private static class SFactory implements
-			ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> {
-
-		private final AuthorFactory authorFactory;
-		private final BlogFactory blogFactory;
-		private final BlogManager blogManager;
-
-		private SFactory(AuthorFactory authorFactory, BlogFactory BlogFactory,
-				BlogManager BlogManager) {
-			this.authorFactory = authorFactory;
-			this.blogFactory = BlogFactory;
-			this.blogManager = BlogManager;
-		}
-
-		@Override
-		public BdfList encode(Blog f) {
-			return BdfList.of(
-					BdfList.of(
-							f.getAuthor().getName(),
-							f.getAuthor().getPublicKey()
-					)
-			);
-		}
-
-		@Override
-		public Blog get(Transaction txn, GroupId groupId)
-				throws DbException {
-			return blogManager.getBlog(txn, groupId);
-		}
-
-		@Override
-		public Blog parse(BdfList shareable) throws FormatException {
-			Author author = authorFactory
-					.createAuthor(shareable.getList(0).getString(0),
-							shareable.getList(0).getRaw(1));
-			return blogFactory.createBlog(author);
-		}
-
-		@Override
-		public Blog parse(BlogInvitation msg) {
-			Author author = authorFactory.createAuthor(msg.getBlogAuthorName(),
-					msg.getBlogPublicKey());
-			return blogFactory.createBlog(author);
-		}
-
-		@Override
-		public Blog parse(BlogInviteeSessionState state) {
-			Author author = authorFactory
-					.createAuthor(state.getBlogAuthorName(),
-							state.getBlogPublicKey());
-			return blogFactory.createBlog(author);
-		}
-
-		@Override
-		public Blog parse(BlogSharerSessionState state) {
-			Author author = authorFactory
-					.createAuthor(state.getBlogAuthorName(),
-							state.getBlogPublicKey());
-			return blogFactory.createBlog(author);
-		}
-	}
-
-	private static class IFactory implements
-			OldInvitationFactory<BlogInvitation, BlogSharerSessionState> {
-		@Override
-		public BlogInvitation build(GroupId groupId, BdfDictionary d)
-				throws FormatException {
-			return BlogInvitation.from(groupId, d);
-		}
-
-		@Override
-		public BlogInvitation build(BlogSharerSessionState localState,
-				long time) {
-			return new BlogInvitation(localState.getContactGroupId(),
-					localState.getSessionId(), localState.getBlogAuthorName(),
-					localState.getBlogPublicKey(), time,
-					localState.getMessage());
-		}
-	}
-
-	private static class ISFactory implements
-			InviteeSessionStateFactory<Blog, BlogInviteeSessionState> {
-		@Override
-		public BlogInviteeSessionState build(SessionId sessionId,
-				MessageId storageId, GroupId groupId,
-				InviteeSessionState.State state, ContactId contactId,
-				GroupId blogId, BdfDictionary d) throws FormatException {
-			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
-			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
-			MessageId invitationId = new MessageId(d.getRaw(INVITATION_ID));
-			return new BlogInviteeSessionState(sessionId, storageId,
-					groupId, state, contactId, blogId, blogAuthorName,
-					blogPublicKey, invitationId);
-		}
-
-		@Override
-		public BlogInviteeSessionState build(SessionId sessionId,
-				MessageId storageId, GroupId groupId,
-				InviteeSessionState.State state, ContactId contactId,
-				Blog blog, MessageId invitationId) {
-			return new BlogInviteeSessionState(sessionId, storageId,
-					groupId, state, contactId, blog.getId(),
-					blog.getAuthor().getName(), blog.getAuthor().getPublicKey(),
-					invitationId);
-		}
-	}
-
-	private static class SSFactory implements
-			SharerSessionStateFactory<Blog, BlogSharerSessionState> {
-		@Override
-		public BlogSharerSessionState build(SessionId sessionId,
-				MessageId storageId, GroupId groupId,
-				SharerSessionState.State state, ContactId contactId,
-				GroupId blogId, BdfDictionary d) throws FormatException {
-			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
-			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
-			MessageId responseId = null;
-			byte[] responseIdBytes = d.getOptionalRaw(RESPONSE_ID);
-			if (responseIdBytes != null)
-				responseId = new MessageId(responseIdBytes);
-			return new BlogSharerSessionState(sessionId, storageId,
-					groupId, state, contactId, blogId, blogAuthorName,
-					blogPublicKey, responseId);
-		}
-
-		@Override
-		public BlogSharerSessionState build(SessionId sessionId,
-				MessageId storageId, GroupId groupId,
-				SharerSessionState.State state, ContactId contactId,
-				Blog blog) {
-			return new BlogSharerSessionState(sessionId, storageId,
-					groupId, state, contactId, blog.getId(),
-					blog.getAuthor().getName(), blog.getAuthor().getPublicKey(),
-					null);
-		}
-	}
-
-	private static class IRFactory implements
-			InvitationReceivedEventFactory<BlogInviteeSessionState, BlogInvitationRequestReceivedEvent> {
-
-		private final SFactory sFactory;
-
-		private IRFactory(SFactory sFactory) {
-			this.sFactory = sFactory;
-		}
-
-		@Override
-		public BlogInvitationRequestReceivedEvent build(
-				BlogInviteeSessionState localState, long time,
-				@Nullable String msg) {
-			Blog blog = sFactory.parse(localState);
-			ContactId contactId = localState.getContactId();
-			BlogInvitationRequest request =
-					new BlogInvitationRequest(localState.getInvitationId(),
-							localState.getSessionId(),
-							localState.getContactGroupId(), contactId,
-							blog.getAuthor().getName(), msg,
-							localState.getShareableId(), true, false, time,
-							false, false, false, false);
-			return new BlogInvitationRequestReceivedEvent(blog, contactId,
-					request);
-		}
-	}
-
-	private static class IRRFactory implements
-			InvitationResponseReceivedEventFactory<BlogSharerSessionState, BlogInvitationResponseReceivedEvent> {
-		@Override
-		public BlogInvitationResponseReceivedEvent build(
-				BlogSharerSessionState localState, boolean accept, long time) {
-			ContactId c = localState.getContactId();
-			MessageId responseId = localState.getResponseId();
-			if (responseId == null)
-				throw new IllegalStateException("No responseId");
-			BlogInvitationResponse response =
-					new BlogInvitationResponse(responseId,
-							localState.getSessionId(),
-							localState.getContactGroupId(),
-							localState.getContactId(),
-							localState.getShareableId(), accept, time, false,
-							false, false, false);
-			return new BlogInvitationResponseReceivedEvent(c, response);
-		}
-	}
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
index 9dbcecd0821d85e8e7e834bbb8c4eed138e0faef..6f0df8e473b3db7b1e1361850adb2cff997523a8 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
@@ -1,17 +1,16 @@
 package org.briarproject.briar.sharing;
 
 import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.client.ClientHelper;
-import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.Group;
-import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.client.BdfQueueMessageValidator;
+import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogFactory;
 
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
@@ -20,73 +19,35 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_N
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_AUTHOR_NAME;
-import static org.briarproject.briar.api.blog.BlogConstants.BLOG_PUBLIC_KEY;
-import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
-import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL;
-import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
-import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
 
 @Immutable
 @NotNullByDefault
-class BlogSharingValidator extends BdfQueueMessageValidator {
+class BlogSharingValidator extends SharingValidator {
+
+	private final BlogFactory blogFactory;
+	private final AuthorFactory authorFactory;
 
 	@Inject
-	BlogSharingValidator(ClientHelper clientHelper,
-			MetadataEncoder metadataEncoder, Clock clock) {
-		super(clientHelper, metadataEncoder, clock);
+	BlogSharingValidator(MessageEncoder messageEncoder,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
+			Clock clock, BlogFactory blogFactory, AuthorFactory authorFactory) {
+		super(messageEncoder, clientHelper, metadataEncoder, clock);
+		this.blogFactory = blogFactory;
+		this.authorFactory = authorFactory;
 	}
 
 	@Override
-	protected BdfMessageContext validateMessage(Message m, Group g,
-			BdfList body) throws FormatException {
-
-		BdfDictionary d = new BdfDictionary();
-		long type = body.getLong(0);
-		byte[] id = body.getRaw(1);
-		checkLength(id, SessionId.LENGTH);
-
-		if (type == SHARE_MSG_TYPE_INVITATION) {
-			checkSize(body, 3, 4);
-
-			BdfList author = body.getList(2);
-			checkSize(author, 2);
-
-			String authorName = author.getString(0);
-			checkLength(authorName, 1, MAX_AUTHOR_NAME_LENGTH);
-
-			byte[] publicKey = author.getRaw(1);
-			checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
-
-			d.put(BLOG_AUTHOR_NAME, authorName);
-			d.put(BLOG_PUBLIC_KEY, publicKey);
-
-			if (body.size() > 3) {
-				String msg = body.getString(3);
-				checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH);
-				d.put(INVITATION_MSG, msg);
-			}
-		} else {
-			checkSize(body, 2);
-			if (type != SHARE_MSG_TYPE_ACCEPT &&
-					type != SHARE_MSG_TYPE_DECLINE &&
-					type != SHARE_MSG_TYPE_LEAVE &&
-					type != SHARE_MSG_TYPE_ABORT) {
-				throw new FormatException();
-			}
-		}
-		// Return the metadata
-		d.put(TYPE, type);
-		d.put(SESSION_ID, id);
-		d.put(LOCAL, false);
-		d.put(TIME, m.getTimestamp());
-		return new BdfMessageContext(d);
+	protected GroupId validateDescriptor(BdfList descriptor)
+			throws FormatException {
+		checkSize(descriptor, 2);
+		String name = descriptor.getString(0);
+		checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
+		byte[] publicKey = descriptor.getRaw(1);
+		checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
+
+		Author author = authorFactory.createAuthor(name, publicKey);
+		Blog blog = blogFactory.createBlog(author);
+		return blog.getId();
 	}
+
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationReceivedEventFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationReceivedEventFactory.java
deleted file mode 100644
index 07d81557e680b4aaa640e1cf28a73f30f967e713..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationReceivedEventFactory.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
-
-import javax.annotation.Nullable;
-
-@Deprecated
-@NotNullByDefault
-interface InvitationReceivedEventFactory<IS extends InviteeSessionState, IR extends InvitationRequestReceivedEvent> {
-
-	IR build(IS localState, long time, @Nullable String msg);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationResponseReceivedEventFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationResponseReceivedEventFactory.java
deleted file mode 100644
index 2a36e36aa5547048b86988a93b85141d3e323bc2..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InvitationResponseReceivedEventFactory.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
-
-@Deprecated
-@NotNullByDefault
-interface InvitationResponseReceivedEventFactory<SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent> {
-
-	IRR build(SS localState, boolean accept, long time);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeEngine.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeEngine.java
deleted file mode 100644
index bdb0be196dc2589beabf6ad2a3650a4a64f07b64..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeEngine.java
+++ /dev/null
@@ -1,242 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.event.Event;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.briar.api.client.ProtocolEngine;
-import org.briarproject.briar.api.sharing.SharingMessage.Invitation;
-import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingMessage.BaseMessage;
-import static org.briarproject.briar.api.sharing.SharingMessage.SimpleMessage;
-
-@Deprecated
-@Immutable
-@NotNullByDefault
-class InviteeEngine<IS extends InviteeSessionState, IR extends InvitationRequestReceivedEvent>
-		implements ProtocolEngine<InviteeSessionState.Action, IS, BaseMessage> {
-
-	private static final Logger LOG =
-			Logger.getLogger(InviteeEngine.class.getName());
-
-	private final InvitationReceivedEventFactory<IS, IR>
-			invitationReceivedEventFactory;
-	private final Clock clock;
-
-	InviteeEngine(
-			InvitationReceivedEventFactory<IS, IR> invitationReceivedEventFactory,
-			Clock clock) {
-		this.invitationReceivedEventFactory = invitationReceivedEventFactory;
-		this.clock = clock;
-	}
-
-	@Override
-	public StateUpdate<IS, BaseMessage> onLocalAction(
-			IS localState, InviteeSessionState.Action action) {
-
-		try {
-			InviteeSessionState.State currentState = localState.getState();
-			InviteeSessionState.State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			if (action == InviteeSessionState.Action.LOCAL_ABORT &&
-					currentState != InviteeSessionState.State.ERROR) {
-				return abortSession(currentState, localState);
-			}
-
-			if (nextState == InviteeSessionState.State.ERROR) {
-				if (LOG.isLoggable(WARNING)) {
-					LOG.warning("Error: Invalid action in state " +
-							currentState.name());
-				}
-				return noUpdate(localState, true);
-			}
-			List<BaseMessage> messages;
-			List<Event> events = Collections.emptyList();
-
-			if (action == InviteeSessionState.Action.LOCAL_ACCEPT ||
-					action == InviteeSessionState.Action.LOCAL_DECLINE) {
-				BaseMessage msg;
-				if (action == InviteeSessionState.Action.LOCAL_ACCEPT) {
-					localState.setTask(TASK_ADD_SHARED_SHAREABLE);
-					msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
-							localState.getContactGroupId(), localState.getSessionId(),
-							clock.currentTimeMillis());
-				} else {
-					localState.setTask(
-							TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US);
-					msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
-							localState.getContactGroupId(), localState.getSessionId(),
-							clock.currentTimeMillis());
-				}
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
-			} else if (action == InviteeSessionState.Action.LOCAL_LEAVE) {
-				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
-						localState.getContactGroupId(), localState.getSessionId(),
-						clock.currentTimeMillis());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
-			} else {
-				throw new IllegalArgumentException("Unknown Local Action");
-			}
-			return new StateUpdate<IS, BaseMessage>(false,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	@Override
-	public StateUpdate<IS, BaseMessage> onMessageReceived(
-			IS localState, BaseMessage msg) {
-
-		try {
-			InviteeSessionState.State currentState = localState.getState();
-			InviteeSessionState.Action action =
-					InviteeSessionState.Action.getRemote(msg.getType());
-			InviteeSessionState.State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			logMessageReceived(currentState, nextState, msg.getType(), msg);
-
-			if (nextState == InviteeSessionState.State.ERROR) {
-				if (currentState != InviteeSessionState.State.ERROR) {
-					return abortSession(currentState, localState);
-				} else {
-					return noUpdate(localState, true);
-				}
-			}
-
-			List<BaseMessage> messages = Collections.emptyList();
-			List<Event> events = Collections.emptyList();
-			boolean deleteMsg = false;
-
-			if (currentState == InviteeSessionState.State.LEFT) {
-				// ignore and delete messages coming in while in that state
-				deleteMsg = true;
-			}
-			// the sharer left the forum she had shared with us
-			else if (action == InviteeSessionState.Action.REMOTE_LEAVE &&
-					currentState == InviteeSessionState.State.FINISHED) {
-				localState.setTask(TASK_UNSHARE_SHAREABLE_SHARED_WITH_US);
-			} else if (currentState == InviteeSessionState.State.FINISHED) {
-				// ignore and delete messages coming in while in that state
-				// note that LEAVE is possible, but was handled above
-				deleteMsg = true;
-			}
-			// the sharer left the forum before we couldn't even respond
-			else if (action == InviteeSessionState.Action.REMOTE_LEAVE) {
-				localState.setTask(
-						TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US);
-			}
-			// we have just received our invitation
-			else if (action == InviteeSessionState.Action.REMOTE_INVITATION) {
-				localState.setTask(TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US);
-				Invitation invitation = (Invitation) msg;
-				Event event = invitationReceivedEventFactory.build(localState,
-						msg.getTime(), invitation.getMessage());
-				events = Collections.singletonList(event);
-			} else {
-				throw new IllegalArgumentException("Bad state");
-			}
-			return new StateUpdate<IS, BaseMessage>(deleteMsg,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	private void logLocalAction(InviteeSessionState.State state,
-			InviteeSessionState localState, BaseMessage msg) {
-
-		if (!LOG.isLoggable(INFO)) return;
-
-		String a = "response";
-		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(InviteeSessionState.State currentState,
-			InviteeSessionState.State nextState,
-			long type, BaseMessage msg) {
-
-		if (!LOG.isLoggable(INFO)) return;
-
-		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<IS, BaseMessage> onMessageDelivered(
-			IS localState, BaseMessage delivered) {
-		try {
-			return noUpdate(localState, false);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		}
-	}
-
-	private StateUpdate<IS, BaseMessage> abortSession(
-			InviteeSessionState.State currentState, IS localState)
-			throws FormatException {
-
-		if (LOG.isLoggable(WARNING)) {
-			LOG.warning("Aborting protocol session " +
-					localState.getSessionId().hashCode() +
-					" in state " + currentState.name());
-		}
-		localState.setState(InviteeSessionState.State.ERROR);
-		BaseMessage msg =
-				new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getContactGroupId(),
-						localState.getSessionId(), clock.currentTimeMillis());
-		List<BaseMessage> messages = Collections.singletonList(msg);
-
-		List<Event> events = Collections.emptyList();
-
-		return new StateUpdate<IS, BaseMessage>(false, false,
-				localState, messages, events);
-	}
-
-	private StateUpdate<IS, BaseMessage> noUpdate(
-			IS localState, boolean delete) throws FormatException {
-
-		return new StateUpdate<IS, BaseMessage>(delete, false,
-				localState, Collections.<BaseMessage>emptyList(),
-				Collections.<Event>emptyList());
-	}
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeSessionState.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeSessionState.java
deleted file mode 100644
index efd35e5be85b6adb26ffabb864b9f7d7d333ed40..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeSessionState.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import javax.annotation.concurrent.NotThreadSafe;
-
-import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.IS_SHARER;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.STATE;
-import static org.briarproject.briar.sharing.InviteeSessionState.Action.LOCAL_ACCEPT;
-import static org.briarproject.briar.sharing.InviteeSessionState.Action.LOCAL_DECLINE;
-import static org.briarproject.briar.sharing.InviteeSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.briar.sharing.InviteeSessionState.Action.REMOTE_INVITATION;
-import static org.briarproject.briar.sharing.InviteeSessionState.Action.REMOTE_LEAVE;
-
-@Deprecated
-@NotThreadSafe
-@NotNullByDefault
-public abstract class InviteeSessionState extends SharingSessionState {
-
-	private State state;
-	private final MessageId invitationId;
-
-	public InviteeSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId,
-			GroupId shareableId, MessageId invitationId) {
-
-		super(sessionId, storageId, groupId, contactId, shareableId);
-		this.state = state;
-		this.invitationId = invitationId;
-	}
-
-	@Override
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = super.toBdfDictionary();
-		d.put(STATE, getState().getValue());
-		d.put(IS_SHARER, false);
-		d.put(INVITATION_ID, invitationId);
-		return d;
-	}
-
-	public void setState(State state) {
-		this.state = state;
-	}
-
-	public State getState() {
-		return state;
-	}
-
-	public MessageId getInvitationId() {
-		return invitationId;
-	}
-
-	@Immutable
-	@NotNullByDefault
-	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;
-		}
-	}
-
-	@NotNullByDefault
-	public enum Action {
-
-		LOCAL_ACCEPT,
-		LOCAL_DECLINE,
-		LOCAL_LEAVE,
-		LOCAL_ABORT,
-		REMOTE_INVITATION,
-		REMOTE_LEAVE,
-		REMOTE_ABORT;
-
-		@Nullable
-		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/main/java/org/briarproject/briar/sharing/InviteeSessionStateFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeSessionStateFactory.java
deleted file mode 100644
index 5c827921e883a94ce0123ac08b0b8c2ec66fea79..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/InviteeSessionStateFactory.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.Shareable;
-
-@Deprecated
-interface InviteeSessionStateFactory<S extends Shareable, IS extends InviteeSessionState> {
-
-	IS build(SessionId sessionId, MessageId storageId, GroupId groupId,
-			InviteeSessionState.State state, ContactId contactId,
-			GroupId shareableId, BdfDictionary d) throws FormatException;
-
-	IS build(SessionId sessionId, MessageId storageId, GroupId groupId,
-			InviteeSessionState.State state, ContactId contactId, S shareable,
-			MessageId invitationId);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java
deleted file mode 100644
index 7f127a1c826251c03914e9479d10a32b74edd6b3..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/OldInvitationFactory.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.briar.api.sharing.SharingMessage;
-
-@Deprecated
-@NotNullByDefault
-interface OldInvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState>
-		extends org.briarproject.briar.api.sharing.InvitationFactory<I> {
-
-	I build(SS localState, long time);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java
deleted file mode 100644
index 613ee0bed8aa2570a15b45ccab6c579a4df3fa5d..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/OldSharingManagerImpl.java
+++ /dev/null
@@ -1,1052 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.Bytes;
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.client.ClientHelper;
-import org.briarproject.bramble.api.client.ContactGroupFactory;
-import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
-import org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfEntry;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.data.MetadataParser;
-import org.briarproject.bramble.api.db.DatabaseComponent;
-import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.db.Metadata;
-import org.briarproject.bramble.api.db.NoSuchMessageException;
-import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.event.Event;
-import org.briarproject.bramble.api.identity.LocalAuthor;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.Client;
-import org.briarproject.bramble.api.sync.ClientId;
-import org.briarproject.bramble.api.sync.Group;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.Message;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.api.sync.MessageStatus;
-import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.util.StringUtils;
-import org.briarproject.briar.api.client.MessageQueueManager;
-import org.briarproject.briar.api.client.MessageTracker;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.InvitationMessage;
-import org.briarproject.briar.api.sharing.Shareable;
-import org.briarproject.briar.api.sharing.SharingInvitationItem;
-import org.briarproject.briar.api.sharing.SharingManager;
-import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
-import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
-import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
-import org.briarproject.briar.client.ConversationClientImpl;
-
-import java.io.IOException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
-import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
-import static org.briarproject.briar.api.client.ProtocolEngine.StateUpdate;
-import static org.briarproject.briar.api.sharing.SharingConstants.CONTACT_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.IS_SHARER;
-import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL;
-import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHAREABLE_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARING_SALT_LENGTH;
-import static org.briarproject.briar.api.sharing.SharingConstants.STATE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
-import static org.briarproject.briar.api.sharing.SharingConstants.TO_BE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
-import static org.briarproject.briar.api.sharing.SharingMessage.BaseMessage;
-import static org.briarproject.briar.api.sharing.SharingMessage.Invitation;
-import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
-import static org.briarproject.briar.sharing.InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
-
-@Deprecated
-@NotNullByDefault
-abstract class OldSharingManagerImpl<S extends Shareable, I extends Invitation, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationRequestReceivedEvent, IRR extends InvitationResponseReceivedEvent>
-		extends ConversationClientImpl
-		implements SharingManager<S>, Client, AddContactHook,
-		RemoveContactHook {
-
-	private static final Logger LOG =
-			Logger.getLogger(OldSharingManagerImpl.class.getName());
-
-	private final MessageQueueManager messageQueueManager;
-	private final MetadataEncoder metadataEncoder;
-	private final SecureRandom random;
-	private final ContactGroupFactory contactGroupFactory;
-	private final Clock clock;
-	private final Group localGroup;
-
-	OldSharingManagerImpl(DatabaseComponent db,
-			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
-			MetadataParser metadataParser, MetadataEncoder metadataEncoder,
-			SecureRandom random, ContactGroupFactory contactGroupFactory,
-			MessageTracker messageTracker, Clock clock) {
-		super(db, clientHelper, metadataParser, messageTracker);
-		this.messageQueueManager = messageQueueManager;
-		this.metadataEncoder = metadataEncoder;
-		this.random = random;
-		this.contactGroupFactory = contactGroupFactory;
-		this.clock = clock;
-		localGroup = contactGroupFactory.createLocalGroup(getClientId());
-	}
-
-	protected abstract ClientId getClientId();
-
-	protected abstract InvitationMessage createInvitationRequest(MessageId id,
-			I msg, ContactId contactId, GroupId blogId, boolean available,
-			boolean canBeOpened, long time, boolean local, boolean sent,
-			boolean seen, boolean read);
-
-	protected abstract InvitationMessage createInvitationResponse(MessageId id,
-			SessionId sessionId, GroupId groupId, ContactId contactId,
-			GroupId blogId, boolean accept, long time, boolean local,
-			boolean sent, boolean seen, boolean read);
-
-	protected abstract ShareableFactory<S, I, IS, SS> getSFactory();
-
-	protected abstract OldInvitationFactory<I, SS> getIFactory();
-
-	protected abstract InviteeSessionStateFactory<S, IS> getISFactory();
-
-	protected abstract SharerSessionStateFactory<S, SS> getSSFactory();
-
-	protected abstract InvitationReceivedEventFactory<IS, IR> getIRFactory();
-
-	protected abstract InvitationResponseReceivedEventFactory<SS, IRR> getIRRFactory();
-
-	@Override
-	public void createLocalState(Transaction txn) throws DbException {
-		db.addGroup(txn, localGroup);
-		// Ensure we've set things up for any pre-existing contacts
-		for (Contact c : db.getContacts(txn)) addingContact(txn, c);
-	}
-
-	@Override
-	public void addingContact(Transaction txn, Contact c) throws DbException {
-		try {
-			// Create a group to share with the contact
-			Group g = getContactGroup(c);
-			// Return if we've already set things up for this contact
-			if (db.containsGroup(txn, g.getId())) return;
-			// Store the group and share it with the contact
-			db.addGroup(txn, g);
-			db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
-			// Attach the contact ID to the group
-			BdfDictionary meta = new BdfDictionary();
-			meta.put(CONTACT_ID, c.getId().getInt());
-			meta.put(TO_BE_SHARED_BY_US, new BdfList());
-			meta.put(SHARED_BY_US, new BdfList());
-			meta.put(SHARED_WITH_US, new BdfList());
-			clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
-		} catch (FormatException e) {
-			throw new DbException(e);
-		}
-	}
-
-	@Override
-	public void removingContact(Transaction txn, Contact c) throws DbException {
-		// query for this contact c
-		BdfDictionary query = BdfDictionary.of(
-				new BdfEntry(CONTACT_ID, c.getId().getInt())
-		);
-
-		// clean up session states with that contact from localGroup
-		try {
-			Map<MessageId, BdfDictionary> map = clientHelper
-					.getMessageMetadataAsDictionary(txn, localGroup.getId(),
-							query);
-			for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
-				deleteMessage(txn, entry.getKey());
-			}
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		}
-
-		// remove the contact group (all messages will be removed with it)
-		db.removeGroup(txn, getContactGroup(c));
-	}
-
-	@Override
-	protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
-			BdfDictionary d) throws DbException, FormatException {
-
-		BaseMessage msg = BaseMessage.from(getIFactory(), m.getGroupId(), d);
-		SessionId sessionId = msg.getSessionId();
-
-		if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
-			// we are an invitee who just received a new invitation
-			boolean stateExists = true;
-			try {
-				// check if we have a session with that ID already
-				getSessionState(txn, sessionId, false);
-			} catch (FormatException e) {
-				// this is what we would expect under normal circumstances
-				stateExists = false;
-			}
-			// check if we already have a state with that sessionId
-			if (stateExists) throw new FormatException();
-
-			// check if shareable can be shared
-			I invitation = (I) msg;
-			S f = getSFactory().parse(invitation);
-			ContactId contactId = getContactId(txn, m.getGroupId());
-			Contact contact = db.getContact(txn, contactId);
-			if (!canBeShared(txn, f.getId(), contact))
-				checkForRaceCondition(txn, f, contact);
-
-			// initialize state and process invitation
-			IS state = initializeInviteeState(txn, contactId, invitation,
-					m.getId());
-			InviteeEngine<IS, IR> engine =
-					new InviteeEngine<IS, IR>(getIRFactory(), clock);
-			processInviteeStateUpdate(txn, m.getId(),
-					engine.onMessageReceived(state, msg));
-			messageTracker.trackIncomingMessage(txn, m);
-		} else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
-				msg.getType() == SHARE_MSG_TYPE_DECLINE) {
-			// we are a sharer who just received a response
-			SS state = getSessionStateForSharer(txn, sessionId);
-			state.setResponseId(m.getId());
-			SharerEngine<I, SS, IRR> engine =
-					new SharerEngine<I, SS, IRR>(getIFactory(),
-							getIRRFactory(), clock);
-			processSharerStateUpdate(txn, m.getId(),
-					engine.onMessageReceived(state, msg));
-			messageTracker.trackIncomingMessage(txn, m);
-		} 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
-			SharingSessionState s = getSessionState(txn, sessionId, true);
-			if (s instanceof SharerSessionState) {
-				// we are a sharer and the invitee wants to leave or abort
-				SS state = (SS) s;
-				SharerEngine<I, SS, IRR> engine =
-						new SharerEngine<I, SS, IRR>(getIFactory(),
-								getIRRFactory(), clock);
-				processSharerStateUpdate(txn, m.getId(),
-						engine.onMessageReceived(state, msg));
-			} else {
-				// we are an invitee and the sharer wants to leave or abort
-				IS state = (IS) s;
-				InviteeEngine<IS, IR> engine =
-						new InviteeEngine<IS, IR>(getIRFactory(), clock);
-				processInviteeStateUpdate(txn, m.getId(),
-						engine.onMessageReceived(state, msg));
-			}
-		} else {
-			// message has passed validator, so that should never happen
-			throw new AssertionError("Illegal Sharing Message");
-		}
-		// don't share message as other party already has it
-		return false;
-	}
-
-	@Override
-	public void sendInvitation(GroupId groupId, ContactId contactId,
-			@Nullable String msg, long timestamp) throws DbException {
-
-		Transaction txn = db.startTransaction(false);
-		try {
-			// initialize local state for sharer
-			S f = getSFactory().get(txn, groupId);
-			SS localState = initializeSharerState(txn, f, contactId);
-
-			// add invitation message to local state to be available for engine
-			if (!StringUtils.isNullOrEmpty(msg)) {
-				int msgLength = StringUtils.toUtf8(msg).length;
-				if (msgLength > MAX_INVITATION_MESSAGE_LENGTH)
-					throw new IllegalArgumentException();
-				localState.setMessage(msg);
-			}
-
-			// start engine and process its state update
-			SharerEngine<I, SS, IRR> engine =
-					new SharerEngine<I, SS, IRR>(getIFactory(),
-							getIRRFactory(), clock);
-			StateUpdate<SS, BaseMessage> update =
-					engine.onLocalAction(localState,
-							SharerSessionState.Action.LOCAL_INVITATION);
-			processSharerStateUpdate(txn, null, update);
-
-			// track message
-			// TODO handle this properly without engine hacks (#376)
-			long time = update.toSend.get(0).getTime();
-			messageTracker.trackMessage(txn, localState.getContactGroupId(), time,
-					true);
-
-			db.commitTransaction(txn);
-		} catch (FormatException e) {
-			throw new DbException();
-		} finally {
-			db.endTransaction(txn);
-		}
-	}
-
-	@Override
-	public void respondToInvitation(S f, Contact c, boolean accept)
-			throws DbException {
-
-		Transaction txn = db.startTransaction(false);
-		try {
-			// find session state based on shareable
-			IS localState = getSessionStateForResponse(txn, f, c);
-			respondToInvitation(txn, localState, accept);
-			db.commitTransaction(txn);
-		} catch (FormatException e) {
-			throw new DbException(e);
-		} finally {
-			db.endTransaction(txn);
-		}
-	}
-
-	@Override
-	public void respondToInvitation(ContactId c, SessionId id, boolean accept)
-			throws DbException {
-
-		Transaction txn = db.startTransaction(false);
-		try {
-			IS localState = (IS) getSessionState(txn, id, true);
-			respondToInvitation(txn, localState, accept);
-			db.commitTransaction(txn);
-		} catch (FormatException e) {
-			throw new DbException(e);
-		} finally {
-			db.endTransaction(txn);
-		}
-	}
-
-	private void respondToInvitation(Transaction txn, IS localState,
-			boolean accept) throws DbException, FormatException {
-		// define action
-		InviteeSessionState.Action localAction;
-		if (accept) {
-			localAction = InviteeSessionState.Action.LOCAL_ACCEPT;
-		} else {
-			localAction = InviteeSessionState.Action.LOCAL_DECLINE;
-		}
-
-		// start engine and process its state update
-		InviteeEngine<IS, IR> engine =
-				new InviteeEngine<IS, IR>(getIRFactory(), clock);
-		StateUpdate<IS, BaseMessage> update =
-				engine.onLocalAction(localState, localAction);
-		processInviteeStateUpdate(txn, null, update);
-
-		// track message
-		// TODO handle this properly without engine hacks (#376)
-		long time = update.toSend.get(0).getTime();
-		messageTracker.trackMessage(txn, localState.getContactGroupId(), time, true);
-	}
-
-	@Override
-	public Collection<InvitationMessage> getInvitationMessages(
-			ContactId contactId)
-			throws DbException {
-
-		Transaction txn = db.startTransaction(true);
-		try {
-			Contact contact = db.getContact(txn, contactId);
-			Group group = getContactGroup(contact);
-
-			Collection<InvitationMessage> list =
-					new ArrayList<InvitationMessage>();
-			Map<MessageId, BdfDictionary> map = clientHelper
-					.getMessageMetadataAsDictionary(txn, group.getId());
-			for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
-				BdfDictionary d = m.getValue();
-				long type = d.getLong(TYPE);
-				if (type == SHARE_MSG_TYPE_LEAVE ||
-						type == SHARE_MSG_TYPE_ABORT) continue;
-				try {
-					MessageStatus status =
-							db.getMessageStatus(txn, contactId, m.getKey());
-					SharingSessionState s;
-					long time = d.getLong(TIME);
-					boolean local = d.getBoolean(LOCAL);
-					boolean read = d.getBoolean(MSG_KEY_READ, false);
-					boolean available = false, canBeOpened = false;
-
-					if (type == SHARE_MSG_TYPE_INVITATION) {
-						I msg = getIFactory().build(group.getId(), d);
-						SessionId sessionId = msg.getSessionId();
-						s = getSessionState(txn, sessionId, true);
-						if (!local) {
-							// figure out whether the shareable is still available
-							if (!(s instanceof InviteeSessionState))
-								continue;
-							available = ((InviteeSessionState) s).getState() ==
-									AWAIT_LOCAL_RESPONSE;
-							if (!available) {
-								canBeOpened = db.containsGroup(txn,
-										s.getShareableId());
-							}
-						}
-						InvitationMessage im =
-								createInvitationRequest(m.getKey(), msg,
-										contactId, s.getShareableId(),
-										available, canBeOpened, time, local,
-										status.isSent(), status.isSeen(), read);
-						list.add(im);
-					} else if (type == SHARE_MSG_TYPE_ACCEPT ||
-							type == SHARE_MSG_TYPE_DECLINE) {
-						boolean accept = type == SHARE_MSG_TYPE_ACCEPT;
-						BaseMessage msg = BaseMessage
-								.from(getIFactory(), group.getId(), d);
-						SessionId sessionId = msg.getSessionId();
-						s = getSessionState(txn, sessionId, true);
-						InvitationMessage im =
-								createInvitationResponse(m.getKey(), sessionId,
-										group.getId(), contactId,
-										s.getShareableId(), accept, time, local,
-										status.isSent(), status.isSeen(), read);
-						list.add(im);
-					} else {
-						throw new RuntimeException("Unexpected Message Type");
-					}
-				} catch (FormatException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-			db.commitTransaction(txn);
-			return list;
-		} catch (FormatException e) {
-			throw new DbException(e);
-		} finally {
-			db.endTransaction(txn);
-		}
-	}
-
-	@Override
-	public Collection<SharingInvitationItem> getInvitations()
-			throws DbException {
-		List<SharingInvitationItem> invitations =
-				new ArrayList<SharingInvitationItem>();
-		Transaction txn = db.startTransaction(true);
-		try {
-			Set<S> shareables = new HashSet<S>();
-			Map<GroupId, Collection<Contact>> newSharers =
-					new HashMap<GroupId, Collection<Contact>>();
-			Collection<Contact> contacts = db.getContacts(txn);
-
-			// get invitations from each contact
-			for (Contact contact : contacts) {
-				Collection<S> newShareables = getInvited(txn, contact);
-				shareables.addAll(newShareables);
-				for (S s : newShareables) {
-					if (newSharers.containsKey(s.getId())) {
-						newSharers.get(s.getId()).add(contact);
-					} else {
-						Collection<Contact> c = new ArrayList<Contact>();
-						c.add(contact);
-						newSharers.put(s.getId(), c);
-					}
-				}
-			}
-			// construct InvitationItem objects
-			for (S s : shareables) {
-				Collection<Contact> newS = newSharers.get(s.getId());
-				boolean subscribed = db.containsGroup(txn, s.getId());
-				SharingInvitationItem invitation =
-						new SharingInvitationItem(s, subscribed, newS);
-				invitations.add(invitation);
-			}
-			db.commitTransaction(txn);
-			return invitations;
-		} catch (FormatException e) {
-			throw new DbException(e);
-		} finally {
-			db.endTransaction(txn);
-		}
-	}
-
-	private Collection<S> getInvited(Transaction txn, Contact contact)
-			throws DbException, FormatException {
-
-		// query for all external invitations
-		BdfDictionary query = BdfDictionary.of(
-				new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION),
-				new BdfEntry(LOCAL, false)
-		);
-		Group group = getContactGroup(contact);
-
-		Set<S> invited = new HashSet<S>();
-		Map<MessageId, BdfDictionary> map = clientHelper
-				.getMessageMetadataAsDictionary(txn, group.getId(), query);
-		for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
-			BdfDictionary d = m.getValue();
-			try {
-				I msg = getIFactory().build(group.getId(), d);
-				IS iss = (IS) getSessionState(txn, msg.getSessionId(), true);
-				// get and add the shareable if the invitation is unanswered
-				if (iss.getState().equals(AWAIT_LOCAL_RESPONSE)) {
-					S s = getSFactory().parse(iss);
-					invited.add(s);
-				}
-			} catch (FormatException e) {
-				if (LOG.isLoggable(WARNING))
-					LOG.log(WARNING, e.toString(), e);
-			}
-		}
-		return invited;
-	}
-
-	@Override
-	public Collection<Contact> getSharedWith(GroupId g) throws DbException {
-		try {
-			List<Contact> shared = new ArrayList<Contact>();
-			Transaction txn = db.startTransaction(true);
-			try {
-				for (Contact c : db.getContacts(txn)) {
-					GroupId contactGroup = getContactGroup(c).getId();
-					if (listContains(txn, contactGroup, g, SHARED_BY_US))
-						shared.add(c);
-					else if (listContains(txn, contactGroup, g, SHARED_WITH_US))
-						shared.add(c);
-				}
-				db.commitTransaction(txn);
-			} finally {
-				db.endTransaction(txn);
-			}
-			return shared;
-		} catch (FormatException e) {
-			throw new DbException(e);
-		}
-	}
-
-	@Override
-	public boolean canBeShared(GroupId g, Contact c) throws DbException {
-		boolean canBeShared;
-		Transaction txn = db.startTransaction(true);
-		try {
-			canBeShared = canBeShared(txn, g, c);
-			db.commitTransaction(txn);
-		} finally {
-			db.endTransaction(txn);
-		}
-		return canBeShared;
-	}
-
-	protected boolean canBeShared(Transaction txn, GroupId g, Contact c)
-			throws DbException {
-
-		try {
-			GroupId contactGroup = getContactGroup(c).getId();
-			return !listContains(txn, contactGroup, g, SHARED_BY_US) &&
-					!listContains(txn, contactGroup, g, SHARED_WITH_US) &&
-					!listContains(txn, contactGroup, g, TO_BE_SHARED_BY_US);
-		} catch (FormatException e) {
-			throw new DbException(e);
-		}
-	}
-
-	void removingShareable(Transaction txn, S f) throws DbException {
-		try {
-			for (Contact c : db.getContacts(txn)) {
-				GroupId g = getContactGroup(c).getId();
-				if (removeFromList(txn, g, TO_BE_SHARED_BY_US, f)) {
-					leaveShareable(txn, c.getId(), f);
-				}
-				if (removeFromList(txn, g, SHARED_BY_US, f)) {
-					leaveShareable(txn, c.getId(), f);
-				}
-				if (removeFromList(txn, g, SHARED_WITH_US, f)) {
-					leaveShareable(txn, c.getId(), f);
-				}
-			}
-		} catch (IOException e) {
-			throw new DbException(e);
-		}
-	}
-
-	private void checkForRaceCondition(Transaction txn, S f, Contact c)
-			throws FormatException, DbException {
-
-		GroupId contactGroup = getContactGroup(c).getId();
-		if (!listContains(txn, contactGroup, f.getId(), TO_BE_SHARED_BY_US))
-			// no race-condition, this invitation is invalid
-			throw new FormatException();
-
-		// we have an invitation race condition
-		LocalAuthor author = db.getLocalAuthor(txn, c.getLocalAuthorId());
-		Bytes ourKey = new Bytes(author.getPublicKey());
-		Bytes theirKey = new Bytes(c.getAuthor().getPublicKey());
-
-		// determine which invitation takes precedence
-		boolean alice = ourKey.compareTo(theirKey) < 0;
-
-		if (alice) {
-			// our own invitation takes precedence, so just delete Bob's
-			LOG.info(
-					"Invitation race-condition: We are Alice deleting Bob's invitation.");
-			throw new FormatException();
-		} else {
-			// we are Bob, so we need to "take back" our own invitation
-			LOG.info(
-					"Invitation race-condition: We are Bob taking back our invitation.");
-			SharingSessionState state =
-					getSessionStateForLeaving(txn, f, c.getId());
-			if (state instanceof SharerSessionState) {
-				//SharerEngine engine = new SharerEngine();
-				//processSharerStateUpdate(txn, null,
-				//		engine.onLocalAction((SharerSessionState) state,
-				//				Action.LOCAL_LEAVE));
-
-				// simply remove from list instead of involving engine
-				removeFromList(txn, contactGroup, TO_BE_SHARED_BY_US, f);
-				// TODO here we could also remove the old session state
-				//      and invitation message
-			}
-		}
-
-	}
-
-	private SS initializeSharerState(Transaction txn, S f,
-			ContactId contactId) throws FormatException, DbException {
-
-		Contact c = db.getContact(txn, contactId);
-		Group group = getContactGroup(c);
-
-		// create local message to keep engine state
-		long now = clock.currentTimeMillis();
-		Bytes salt = new Bytes(new byte[SHARING_SALT_LENGTH]);
-		random.nextBytes(salt.getBytes());
-		Message m = clientHelper.createMessage(localGroup.getId(), now,
-				BdfList.of(salt));
-		SessionId sessionId = new SessionId(m.getId().getBytes());
-
-		SS s = getSSFactory().build(sessionId, m.getId(), group.getId(),
-				SharerSessionState.State.PREPARE_INVITATION, contactId, f);
-
-		// save local state to database
-		BdfDictionary d = s.toBdfDictionary();
-		clientHelper.addLocalMessage(txn, m, d, false);
-
-		return s;
-	}
-
-	private IS initializeInviteeState(Transaction txn,
-			ContactId contactId, I msg, MessageId id)
-			throws FormatException, DbException {
-
-		Contact c = db.getContact(txn, contactId);
-		Group group = getContactGroup(c);
-		S f = getSFactory().parse(msg);
-
-		// create local message to keep engine state
-		long now = clock.currentTimeMillis();
-		Bytes mSalt = new Bytes(new byte[SHARING_SALT_LENGTH]);
-		random.nextBytes(mSalt.getBytes());
-		Message m = clientHelper.createMessage(localGroup.getId(), now,
-				BdfList.of(mSalt));
-
-		IS s = getISFactory()
-				.build(msg.getSessionId(), m.getId(), group.getId(),
-						InviteeSessionState.State.AWAIT_INVITATION, contactId,
-						f, id);
-
-		// save local state to database
-		BdfDictionary d = s.toBdfDictionary();
-		clientHelper.addLocalMessage(txn, m, d, false);
-
-		return s;
-	}
-
-	private SharingSessionState getSessionState(Transaction txn,
-			SessionId sessionId, boolean warn)
-			throws DbException, FormatException {
-
-		try {
-			return getSessionStateForSharer(txn, sessionId);
-		} catch (NoSuchMessageException e) {
-			// State not found directly, so query for state for invitee
-			BdfDictionary query = BdfDictionary.of(
-					new BdfEntry(SESSION_ID, sessionId)
-			);
-
-			Map<MessageId, BdfDictionary> map = clientHelper
-					.getMessageMetadataAsDictionary(txn, localGroup.getId(),
-							query);
-
-			if (map.size() > 1 && LOG.isLoggable(WARNING)) {
-				LOG.warning(
-						"More than one session state found for message with session ID " +
-								Arrays.hashCode(sessionId.getBytes()));
-			}
-			if (map.isEmpty()) {
-				if (warn && LOG.isLoggable(WARNING)) {
-					LOG.warning(
-							"No session state found for message with session ID " +
-									Arrays.hashCode(sessionId.getBytes()));
-				}
-				throw new FormatException();
-			}
-			return SharingSessionState
-					.fromBdfDictionary(getISFactory(), getSSFactory(),
-							map.values().iterator().next());
-		}
-	}
-
-	private SS getSessionStateForSharer(Transaction txn,
-			SessionId sessionId)
-			throws DbException, FormatException {
-
-		// we should be able to get the sharer state directly from sessionId
-		MessageId storageId = new MessageId(sessionId.getBytes());
-		BdfDictionary d =
-				clientHelper.getMessageMetadataAsDictionary(txn, storageId);
-
-		if (!d.getBoolean(IS_SHARER)) throw new FormatException();
-
-		return (SS) SharingSessionState
-				.fromBdfDictionary(getISFactory(), getSSFactory(), d);
-	}
-
-	private IS getSessionStateForResponse(Transaction txn,
-			S f, Contact c) throws DbException, FormatException {
-
-		// query for invitee states for that shareable in state await response
-		BdfDictionary query = BdfDictionary.of(
-				new BdfEntry(IS_SHARER, false),
-				new BdfEntry(CONTACT_ID, c.getId().getInt()),
-				new BdfEntry(SHAREABLE_ID, f.getId()),
-				new BdfEntry(STATE, AWAIT_LOCAL_RESPONSE.getValue())
-		);
-
-		Map<MessageId, BdfDictionary> map = clientHelper
-				.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
-
-		if (map.size() > 1 && LOG.isLoggable(WARNING)) {
-			LOG.warning(
-					"More than one session state found for shareable with ID " +
-							Arrays.hashCode(f.getId().getBytes()) +
-							" in state AWAIT_LOCAL_RESPONSE for contact " +
-							c.getAuthor().getName());
-		}
-		if (map.isEmpty()) {
-			if (LOG.isLoggable(WARNING)) {
-				LOG.warning(
-						"No session state found for shareable with ID " +
-								Arrays.hashCode(f.getId().getBytes()) +
-								" in state AWAIT_LOCAL_RESPONSE");
-			}
-			throw new DbException();
-		}
-		return (IS) SharingSessionState
-				.fromBdfDictionary(getISFactory(), getSSFactory(),
-						map.values().iterator().next());
-	}
-
-	private SharingSessionState getSessionStateForLeaving(Transaction txn,
-			S f, ContactId c) throws DbException, FormatException {
-
-		BdfDictionary query = BdfDictionary.of(
-				new BdfEntry(CONTACT_ID, c.getInt()),
-				new BdfEntry(SHAREABLE_ID, f.getId())
-		);
-		Map<MessageId, BdfDictionary> map = clientHelper
-				.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
-		for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
-			BdfDictionary d = m.getValue();
-			try {
-				SharingSessionState s = SharingSessionState
-						.fromBdfDictionary(getISFactory(), getSSFactory(), d);
-
-				// check that a shareable get be left in current session
-				if (s instanceof SharerSessionState) {
-					SharerSessionState state = (SharerSessionState) s;
-					SharerSessionState.State nextState =
-							state.getState()
-									.next(SharerSessionState.Action.LOCAL_LEAVE);
-					if (nextState != SharerSessionState.State.ERROR) {
-						return state;
-					}
-				} else {
-					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);
-			}
-		}
-		throw new FormatException();
-	}
-
-	private void processStateUpdate(Transaction txn,
-			@Nullable MessageId messageId,
-			StateUpdate<SharingSessionState, BaseMessage> result, S f)
-			throws DbException, FormatException {
-
-		// perform actions based on new local state
-		performTasks(txn, result.localState, f);
-
-		// save new local state
-		MessageId storageId = result.localState.getStorageId();
-		clientHelper.mergeMessageMetadata(txn, storageId,
-				result.localState.toBdfDictionary());
-
-		// send messages
-		for (BaseMessage msg : result.toSend) {
-			sendMessage(txn, msg);
-		}
-
-		// broadcast events
-		for (Event event : result.toBroadcast) {
-			txn.attach(event);
-		}
-
-		// delete message
-		if (result.deleteMessage && messageId != null) {
-			if (LOG.isLoggable(INFO)) {
-				LOG.info("Deleting message with id " + messageId.hashCode());
-			}
-			db.deleteMessage(txn, messageId);
-			db.deleteMessageMetadata(txn, messageId);
-		}
-	}
-
-	private void processSharerStateUpdate(Transaction txn,
-			@Nullable MessageId messageId, StateUpdate<SS, BaseMessage> result)
-			throws DbException, FormatException {
-
-		StateUpdate<SharingSessionState, BaseMessage> r =
-				new StateUpdate<SharingSessionState, BaseMessage>(
-						result.deleteMessage, result.deleteState,
-						result.localState, result.toSend, result.toBroadcast);
-
-		// get shareable for later
-		S f = getSFactory().parse(result.localState);
-
-		processStateUpdate(txn, messageId, r, f);
-	}
-
-	private void processInviteeStateUpdate(Transaction txn,
-			@Nullable MessageId messageId, StateUpdate<IS, BaseMessage> result)
-			throws DbException, FormatException {
-
-		StateUpdate<SharingSessionState, BaseMessage> r =
-				new StateUpdate<SharingSessionState, BaseMessage>(
-						result.deleteMessage, result.deleteState,
-						result.localState, result.toSend, result.toBroadcast);
-
-		// get shareable for later
-		S f = getSFactory().parse(result.localState);
-
-		processStateUpdate(txn, messageId, r, f);
-	}
-
-	private void performTasks(Transaction txn, SharingSessionState localState,
-			S f) throws FormatException, DbException {
-
-		if (localState.getTask() == -1) return;
-
-		// remember task and remove it from localState
-		long task = localState.getTask();
-		localState.setTask(-1);
-
-		// get group ID for later
-		GroupId groupId = localState.getContactGroupId();
-		// get contact ID for later
-		ContactId contactId = localState.getContactId();
-
-		// perform tasks
-		if (task == TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US) {
-			addToList(txn, groupId, SHARED_WITH_US, f);
-		} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US) {
-			removeFromList(txn, groupId, SHARED_WITH_US, f);
-		} else if (task == TASK_ADD_SHARED_SHAREABLE) {
-			// TODO we might want to call the add() method of the respective
-			//      manager here, because blogs add a description for example
-			db.addGroup(txn, f.getGroup());
-			db.setGroupVisibility(txn, contactId, f.getId(), SHARED);
-		} else if (task == TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US) {
-			addToList(txn, groupId, TO_BE_SHARED_BY_US, f);
-		} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US) {
-			removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
-		} else if (task == TASK_SHARE_SHAREABLE) {
-			db.setGroupVisibility(txn, contactId, f.getId(), SHARED);
-			removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
-			addToList(txn, groupId, SHARED_BY_US, f);
-		} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_BY_US) {
-			db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE);
-			removeFromList(txn, groupId, SHARED_BY_US, f);
-			// broadcast event informing UI that contact has left the group
-			ContactLeftShareableEvent
-					e = new ContactLeftShareableEvent(f.getId(), contactId);
-			txn.attach(e);
-		} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_WITH_US) {
-			db.setGroupVisibility(txn, contactId, f.getId(), INVISIBLE);
-			removeFromList(txn, groupId, SHARED_WITH_US, f);
-			// broadcast event informing UI that contact has left the group
-			ContactLeftShareableEvent
-					e = new ContactLeftShareableEvent(f.getId(), contactId);
-			txn.attach(e);
-		}
-	}
-
-	private void sendMessage(Transaction txn, BaseMessage m)
-			throws FormatException, DbException {
-
-		byte[] body = clientHelper.toByteArray(m.toBdfList());
-		Group group = db.getGroup(txn, m.getGroupId());
-
-		// add message itself as metadata
-		BdfDictionary d = m.toBdfDictionary();
-		d.put(LOCAL, true);
-		d.put(TIME, m.getTime());
-		Metadata meta = metadataEncoder.encode(d);
-
-		messageQueueManager
-				.sendMessage(txn, group, m.getTime(), body, meta);
-	}
-
-	@Override
-	public Group getContactGroup(Contact c) {
-		return contactGroupFactory.createContactGroup(getClientId(), c);
-	}
-
-	private ContactId getContactId(Transaction txn, GroupId contactGroupId)
-			throws DbException, FormatException {
-		BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
-				contactGroupId);
-		return new ContactId(meta.getLong(CONTACT_ID).intValue());
-	}
-
-	private void leaveShareable(Transaction txn, ContactId c, S f)
-			throws DbException, FormatException {
-
-		SharingSessionState state = getSessionStateForLeaving(txn, f, c);
-		if (state instanceof SharerSessionState) {
-			SharerSessionState.Action action =
-					SharerSessionState.Action.LOCAL_LEAVE;
-			SharerEngine<I, SS, IRR> engine =
-					new SharerEngine<I, SS, IRR>(getIFactory(),
-							getIRRFactory(), clock);
-			processSharerStateUpdate(txn, null,
-					engine.onLocalAction((SS) state, action));
-		} else {
-			InviteeSessionState.Action action =
-					InviteeSessionState.Action.LOCAL_LEAVE;
-			InviteeEngine<IS, IR> engine =
-					new InviteeEngine<IS, IR>(getIRFactory(), clock);
-			processInviteeStateUpdate(txn, null,
-					engine.onLocalAction((IS) state, action));
-		}
-	}
-
-	private boolean listContains(Transaction txn, GroupId contactGroup,
-			GroupId shareable, String key) throws DbException, FormatException {
-
-		List<S> list = getShareableList(txn, contactGroup, key);
-		for (S f : list) {
-			if (f.getId().equals(shareable)) return true;
-		}
-		return false;
-	}
-
-	private boolean addToList(Transaction txn, GroupId groupId, String key,
-			S f) throws DbException, FormatException {
-
-		List<S> shareables = getShareableList(txn, groupId, key);
-		if (shareables.contains(f)) return false;
-		shareables.add(f);
-		storeShareableList(txn, groupId, key, shareables);
-		return true;
-	}
-
-	private boolean removeFromList(Transaction txn, GroupId groupId, String key,
-			S f) throws DbException, FormatException {
-
-		List<S> shareables = getShareableList(txn, groupId, key);
-		if (shareables.remove(f)) {
-			storeShareableList(txn, groupId, key, shareables);
-			return true;
-		}
-		return false;
-	}
-
-	private List<S> getShareableList(Transaction txn, GroupId groupId,
-			String key) throws DbException, FormatException {
-
-		BdfDictionary metadata =
-				clientHelper.getGroupMetadataAsDictionary(txn, groupId);
-		BdfList list = metadata.getList(key);
-
-		return parseShareableList(list);
-	}
-
-	private void storeShareableList(Transaction txn, GroupId groupId,
-			String key,
-			List<S> shareables) throws DbException, FormatException {
-
-		BdfList list = encodeShareableList(shareables);
-		BdfDictionary metadata = BdfDictionary.of(
-				new BdfEntry(key, list)
-		);
-		clientHelper.mergeGroupMetadata(txn, groupId, metadata);
-	}
-
-	private BdfList encodeShareableList(List<S> shareables) {
-		BdfList shareableList = new BdfList();
-		for (S f : shareables)
-			shareableList.add(getSFactory().encode(f));
-		return shareableList;
-	}
-
-	private List<S> parseShareableList(BdfList list) throws FormatException {
-		List<S> shareables = new ArrayList<S>(list.size());
-		for (int i = 0; i < list.size(); i++) {
-			BdfList shareable = list.getList(i);
-			shareables.add(getSFactory().parse(shareable));
-		}
-		return shareables;
-	}
-
-	private void deleteMessage(Transaction txn, MessageId messageId)
-			throws DbException {
-
-		if (LOG.isLoggable(INFO))
-			LOG.info("Deleting message with ID: " + messageId.hashCode());
-
-		db.deleteMessage(txn, messageId);
-		db.deleteMessageMetadata(txn, messageId);
-	}
-
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
index dd8132d90aec63b30b489e3e3f55934697be584c..ad017942a98cf34d0c24a31b4e69f5d9fa9649ab 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.ProtocolStateException;
 import org.briarproject.briar.api.sharing.Shareable;
+import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
 
 import java.util.Map;
 
@@ -486,6 +487,14 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		// The dependency, if any, must be the last remote message
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abort(txn, s);
+		if (s.getState() == SHARING) {
+			// Broadcast event informing that contact left
+			ContactId contactId = getContactId(txn, s.getContactGroupId());
+			ContactLeftShareableEvent e =
+					new ContactLeftShareableEvent(s.getShareableId(),
+							contactId);
+			txn.attach(e);
+		}
 		// Move to the next state
 		return new Session(nextState, s.getContactGroupId(), s.getShareableId(),
 				s.getLastLocalMessageId(), m.getId(), s.getLocalTimestamp(),
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ShareableFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ShareableFactory.java
deleted file mode 100644
index 367f8882abaa94418b053bbf7113508bd2cccc8e..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ShareableFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.briar.api.sharing.Shareable;
-import org.briarproject.briar.api.sharing.SharingMessage;
-
-@Deprecated
-@NotNullByDefault
-interface ShareableFactory<S extends Shareable, I extends SharingMessage.Invitation, IS extends InviteeSessionState, SS extends SharerSessionState> {
-
-	BdfList encode(S sh);
-
-	S get(Transaction txn, GroupId groupId) throws DbException;
-
-	S parse(BdfList shareable) throws FormatException;
-
-	S parse(I msg);
-
-	S parse(IS state);
-
-	S parse(SS state);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java
deleted file mode 100644
index eb05d87cdffc6b91aaa773bb7f5fc463a9d24f90..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerEngine.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.event.Event;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.briar.api.client.ProtocolEngine;
-import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
-import static org.briarproject.briar.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
-import static org.briarproject.briar.api.sharing.SharingMessage.BaseMessage;
-import static org.briarproject.briar.api.sharing.SharingMessage.Invitation;
-import static org.briarproject.briar.api.sharing.SharingMessage.SimpleMessage;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_ACCEPT;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_DECLINE;
-
-@Deprecated
-@Immutable
-@NotNullByDefault
-class SharerEngine<I extends Invitation, SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent>
-		implements ProtocolEngine<SharerSessionState.Action, SS, BaseMessage> {
-
-	private static final Logger LOG =
-			Logger.getLogger(SharerEngine.class.getName());
-
-	private final OldInvitationFactory<I, SS> invitationFactory;
-	private final InvitationResponseReceivedEventFactory<SS, IRR>
-			invitationResponseReceivedEventFactory;
-	private final Clock clock;
-
-	SharerEngine(OldInvitationFactory<I, SS> invitationFactory,
-			InvitationResponseReceivedEventFactory<SS, IRR> invitationResponseReceivedEventFactory,
-			Clock clock) {
-		this.invitationFactory = invitationFactory;
-		this.invitationResponseReceivedEventFactory =
-				invitationResponseReceivedEventFactory;
-		this.clock = clock;
-	}
-
-	@Override
-	public StateUpdate<SS, BaseMessage> onLocalAction(
-			SS localState, SharerSessionState.Action action) {
-
-		try {
-			SharerSessionState.State currentState = localState.getState();
-			SharerSessionState.State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			if (action == SharerSessionState.Action.LOCAL_ABORT &&
-					currentState != SharerSessionState.State.ERROR) {
-				return abortSession(currentState, localState);
-			}
-
-			if (nextState == SharerSessionState.State.ERROR) {
-				if (LOG.isLoggable(WARNING)) {
-					LOG.warning("Error: Invalid action in state " +
-							currentState.name());
-				}
-				return noUpdate(localState, true);
-			}
-			List<BaseMessage> messages;
-			List<Event> events = Collections.emptyList();
-
-			if (action == SharerSessionState.Action.LOCAL_INVITATION) {
-				BaseMessage msg = invitationFactory.build(localState,
-						clock.currentTimeMillis());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, nextState, msg);
-
-				// remember that we offered to share this forum
-				localState
-						.setTask(TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US);
-			} else if (action == SharerSessionState.Action.LOCAL_LEAVE) {
-				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
-						localState.getContactGroupId(), localState.getSessionId(),
-						clock.currentTimeMillis());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, nextState, msg);
-			} else {
-				throw new IllegalArgumentException("Unknown Local Action");
-			}
-			return new StateUpdate<SS, BaseMessage>(false,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	@Override
-	public StateUpdate<SS, BaseMessage> onMessageReceived(
-			SS localState, BaseMessage msg) {
-
-		try {
-			SharerSessionState.State currentState = localState.getState();
-			SharerSessionState.Action action =
-					SharerSessionState.Action.getRemote(msg.getType());
-			SharerSessionState.State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			logMessageReceived(currentState, nextState, msg.getType(), msg);
-
-			if (nextState == SharerSessionState.State.ERROR) {
-				if (currentState != SharerSessionState.State.ERROR) {
-					return abortSession(currentState, localState);
-				} else {
-					return noUpdate(localState, true);
-				}
-			}
-			List<BaseMessage> messages = Collections.emptyList();
-			List<Event> events = Collections.emptyList();
-			boolean deleteMsg = false;
-
-			if (currentState == SharerSessionState.State.LEFT) {
-				// ignore and delete messages coming in while in that state
-				deleteMsg = true;
-			} else if (action == SharerSessionState.Action.REMOTE_LEAVE) {
-				localState.setTask(TASK_UNSHARE_SHAREABLE_SHARED_BY_US);
-			} else if (currentState == SharerSessionState.State.FINISHED) {
-				// ignore and delete messages coming in while in that state
-				// note that LEAVE is possible, but was handled above
-				deleteMsg = true;
-			}
-			// we have sent our invitation and just got a response
-			else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
-				if (action == REMOTE_ACCEPT) {
-					localState.setTask(TASK_SHARE_SHAREABLE);
-				} else {
-					// this ensures that the forum can be shared again
-					localState.setTask(
-							TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US);
-				}
-				Event event = invitationResponseReceivedEventFactory
-						.build(localState, action == REMOTE_ACCEPT,
-								msg.getTime());
-				events = Collections.singletonList(event);
-			} else {
-				throw new IllegalArgumentException("Bad state");
-			}
-			return new StateUpdate<SS, BaseMessage>(deleteMsg,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	private void logLocalAction(SharerSessionState.State currentState,
-			SharerSessionState.State nextState,
-			BaseMessage msg) {
-
-		if (!LOG.isLoggable(INFO)) return;
-
-		String a = "invitation";
-		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(SharerSessionState.State currentState,
-			SharerSessionState.State nextState,
-			long type, BaseMessage msg) {
-
-		if (!LOG.isLoggable(INFO)) return;
-
-		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<SS, BaseMessage> onMessageDelivered(
-			SS localState, BaseMessage delivered) {
-		try {
-			return noUpdate(localState, false);
-		} catch (FormatException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
-		}
-	}
-
-	private StateUpdate<SS, BaseMessage> abortSession(
-			SharerSessionState.State currentState, SS localState)
-			throws FormatException {
-
-		if (LOG.isLoggable(WARNING)) {
-			LOG.warning("Aborting protocol session " +
-					localState.getSessionId().hashCode() +
-					" in state " + currentState.name());
-		}
-
-		localState.setState(SharerSessionState.State.ERROR);
-		BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
-				localState.getContactGroupId(), localState.getSessionId(),
-				clock.currentTimeMillis());
-		List<BaseMessage> messages = Collections.singletonList(msg);
-
-		List<Event> events = Collections.emptyList();
-
-		return new StateUpdate<SS, BaseMessage>(false, false,
-				localState, messages, events);
-	}
-
-	private StateUpdate<SS, BaseMessage> noUpdate(
-			SS localState, boolean delete)
-			throws FormatException {
-
-		return new StateUpdate<SS, BaseMessage>(delete, false,
-				localState, Collections.<BaseMessage>emptyList(),
-				Collections.<Event>emptyList());
-	}
-
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionState.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionState.java
deleted file mode 100644
index 2579a3f509023345c3168943e4deb91e6c754480..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionState.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.NotThreadSafe;
-
-import static org.briarproject.briar.api.sharing.SharingConstants.IS_SHARER;
-import static org.briarproject.briar.api.sharing.SharingConstants.RESPONSE_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.briar.api.sharing.SharingConstants.STATE;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.LOCAL_INVITATION;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_ACCEPT;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_DECLINE;
-import static org.briarproject.briar.sharing.SharerSessionState.Action.REMOTE_LEAVE;
-
-@Deprecated
-@NotThreadSafe
-@NotNullByDefault
-public abstract class SharerSessionState extends SharingSessionState {
-
-	private State state;
-	@Nullable
-	private String msg = null;
-	@Nullable
-	private MessageId responseId;
-
-	public SharerSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId,
-			GroupId shareableId, @Nullable MessageId responseId) {
-
-		super(sessionId, storageId, groupId, contactId, shareableId);
-		this.state = state;
-		this.responseId = responseId;
-	}
-
-	@Override
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = super.toBdfDictionary();
-		d.put(STATE, getState().getValue());
-		d.put(IS_SHARER, true);
-		if (responseId != null) d.put(RESPONSE_ID, responseId);
-		return d;
-	}
-
-	public void setState(State state) {
-		this.state = state;
-	}
-
-	public State getState() {
-		return state;
-	}
-
-	public void setMessage(String msg) {
-		this.msg = msg;
-	}
-
-	@Nullable
-	public String getMessage() {
-		return msg;
-	}
-
-	public void setResponseId(@Nullable MessageId responseId) {
-		this.responseId = responseId;
-	}
-
-	@Nullable
-	public MessageId getResponseId() {
-		return responseId;
-	}
-
-	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
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionStateFactory.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionStateFactory.java
deleted file mode 100644
index 9be12463f78edd0dc85c3e033da3d54322ae36bf..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharerSessionStateFactory.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.api.sharing.Shareable;
-
-@Deprecated
-@NotNullByDefault
-interface SharerSessionStateFactory<S extends Shareable, SS extends SharerSessionState> {
-
-	SS build(SessionId sessionId, MessageId storageId, GroupId groupId,
-			SharerSessionState.State state, ContactId contactId,
-			GroupId shareableId, BdfDictionary d) throws FormatException;
-
-	SS build(SessionId sessionId, MessageId storageId, GroupId groupId,
-			SharerSessionState.State state, ContactId contactId, S shareable);
-}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
index e0c640e1e6258c1a8d4a905b14a3b840f1069a04..4d8c60a9bc494c3a8fdbe5c493052bfe01e5b002 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingManagerImpl.java
@@ -23,6 +23,7 @@ import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.MessageStatus;
 import org.briarproject.briar.api.client.MessageTracker;
+import org.briarproject.briar.api.client.ProtocolStateException;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.sharing.InvitationMessage;
 import org.briarproject.briar.api.sharing.InvitationRequest;
@@ -211,8 +212,10 @@ abstract class SharingManagerImpl<S extends Shareable>
 		SessionId sessionId = getSessionId(shareableId);
 		Transaction txn = db.startTransaction(false);
 		try {
-			// Look up the session, if there is one
 			Contact contact = db.getContact(txn, contactId);
+			if (!canBeShared(txn, shareableId, contact))
+				throw new ProtocolStateException();
+			// Look up the session, if there is one
 			GroupId contactGroupId = getContactGroup(contact).getId();
 			StoredSession ss = getSession(txn, contactGroupId, sessionId);
 			// Create or parse the session
@@ -400,12 +403,22 @@ abstract class SharingManagerImpl<S extends Shareable>
 
 	@Override
 	public boolean canBeShared(GroupId g, Contact c) throws DbException {
+		Transaction txn = db.startTransaction(true);
+		try {
+			boolean canBeShared = canBeShared(txn, g, c);
+			db.commitTransaction(txn);
+			return canBeShared;
+		} finally {
+			db.endTransaction(txn);
+		}
+	}
+
+	protected boolean canBeShared(Transaction txn, GroupId g, Contact c)
+			throws DbException {
 		GroupId contactGroupId = getContactGroup(c).getId();
 		SessionId sessionId = getSessionId(g);
-		Transaction txn = db.startTransaction(true);
 		try {
 			StoredSession ss = getSession(txn, contactGroupId, sessionId);
-			db.commitTransaction(txn);
 			// If there's no session, we can share the group with the contact
 			if (ss == null) return true;
 			// If the session's in the right state, the contact can be invited
@@ -414,8 +427,6 @@ abstract class SharingManagerImpl<S extends Shareable>
 			return session.getState().canInvite();
 		} catch (FormatException e) {
 			throw new DbException(e);
-		} finally {
-			db.endTransaction(txn);
 		}
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
index e4a24c1b73b1379b6eca6b36ef72e0a8c2c0570b..ecd8c0f61577443e6a9b4987375291401f8312cf 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
@@ -3,12 +3,14 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.data.MetadataEncoder;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogFactory;
 import org.briarproject.briar.api.blog.BlogManager;
 import org.briarproject.briar.api.blog.BlogSharingManager;
-import org.briarproject.briar.api.client.MessageQueueManager;
 import org.briarproject.briar.api.forum.Forum;
 import org.briarproject.briar.api.forum.ForumFactory;
 import org.briarproject.briar.api.forum.ForumManager;
@@ -35,17 +37,32 @@ public class SharingModule {
 		BlogSharingManager blogSharingManager;
 	}
 
+	@Provides
+	MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
+		return messageEncoder;
+	}
+
+	@Provides
+	SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
+		return sessionEncoder;
+	}
+
+	@Provides
+	SessionParser provideSessionParser(SessionParserImpl sessionParser) {
+		return sessionParser;
+	}
+
 	@Provides
 	@Singleton
 	BlogSharingValidator provideBlogSharingValidator(
-			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
-			MetadataEncoder metadataEncoder, Clock clock) {
-
+			ValidationManager validationManager, MessageEncoder messageEncoder,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
+			Clock clock, BlogFactory blogFactory, AuthorFactory authorFactory) {
 		BlogSharingValidator validator =
-				new BlogSharingValidator(clientHelper, metadataEncoder, clock);
-		messageQueueManager.registerMessageValidator(
-				BlogSharingManager.CLIENT_ID, validator);
-
+				new BlogSharingValidator(messageEncoder, clientHelper,
+						metadataEncoder, clock, blogFactory, authorFactory);
+		validationManager.registerMessageValidator(BlogSharingManager.CLIENT_ID,
+				validator);
 		return validator;
 	}
 
@@ -53,14 +70,13 @@ public class SharingModule {
 	@Singleton
 	BlogSharingManager provideBlogSharingManager(
 			LifecycleManager lifecycleManager, ContactManager contactManager,
-			MessageQueueManager messageQueueManager,
+			ValidationManager validationManager,
 			ConversationManager conversationManager, BlogManager blogManager,
 			BlogSharingManagerImpl blogSharingManager) {
-
 		lifecycleManager.registerClient(blogSharingManager);
 		contactManager.registerAddContactHook(blogSharingManager);
 		contactManager.registerRemoveContactHook(blogSharingManager);
-		messageQueueManager.registerIncomingMessageHook(
+		validationManager.registerIncomingMessageHook(
 				BlogSharingManager.CLIENT_ID, blogSharingManager);
 		conversationManager.registerConversationClient(blogSharingManager);
 		blogManager.registerRemoveBlogHook(blogSharingManager);
@@ -68,6 +84,24 @@ public class SharingModule {
 		return blogSharingManager;
 	}
 
+	@Provides
+	MessageParser<Blog> provideBlogMessageParser(
+			BlogMessageParserImpl blogMessageParser) {
+		return blogMessageParser;
+	}
+
+	@Provides
+	ProtocolEngine<Blog> provideBlogProtocolEngine(
+			BlogProtocolEngineImpl blogProtocolEngine) {
+		return blogProtocolEngine;
+	}
+
+	@Provides
+	InvitationFactory<Blog> provideBlogInvitationFactory(
+			BlogInvitationFactoryImpl blogInvitationFactory) {
+		return blogInvitationFactory;
+	}
+
 	@Provides
 	@Singleton
 	ForumSharingValidator provideForumSharingValidator(
@@ -102,27 +136,12 @@ public class SharingModule {
 		return forumSharingManager;
 	}
 
-	@Provides
-	MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
-		return messageEncoder;
-	}
-
 	@Provides
 	MessageParser<Forum> provideForumMessageParser(
 			ForumMessageParserImpl forumMessageParser) {
 		return forumMessageParser;
 	}
 
-	@Provides
-	SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
-		return sessionEncoder;
-	}
-
-	@Provides
-	SessionParser provideSessionParser(SessionParserImpl sessionParser) {
-		return sessionParser;
-	}
-
 	@Provides
 	ProtocolEngine<Forum> provideForumProtocolEngine(
 			ForumProtocolEngineImpl forumProtocolEngine) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingSessionState.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingSessionState.java
deleted file mode 100644
index 77ddbd52c4a5ab780a9f76bbae030abf7b05bcfb..0000000000000000000000000000000000000000
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingSessionState.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.briarproject.briar.sharing;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.contact.ContactId;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.sync.GroupId;
-import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.briar.api.client.SessionId;
-
-import javax.annotation.concurrent.NotThreadSafe;
-
-import static org.briarproject.briar.api.sharing.SharingConstants.CONTACT_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.GROUP_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.IS_SHARER;
-import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.SHAREABLE_ID;
-import static org.briarproject.briar.api.sharing.SharingConstants.STATE;
-import static org.briarproject.briar.api.sharing.SharingConstants.STORAGE_ID;
-
-@Deprecated
-@NotThreadSafe
-@NotNullByDefault
-abstract class SharingSessionState {
-
-	private final SessionId sessionId;
-	private final MessageId storageId;
-	private final GroupId groupId;
-	private final ContactId contactId;
-	private final GroupId shareableId;
-	private int task = -1; // TODO get rid of task, see #376
-
-	SharingSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, ContactId contactId, GroupId shareableId) {
-
-		this.sessionId = sessionId;
-		this.storageId = storageId;
-		this.groupId = groupId;
-		this.contactId = contactId;
-		this.shareableId = shareableId;
-	}
-
-	static SharingSessionState fromBdfDictionary(
-			InviteeSessionStateFactory isFactory,
-			SharerSessionStateFactory ssFactory, 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(SHAREABLE_ID));
-
-		int intState = d.getLong(STATE).intValue();
-		if (d.getBoolean(IS_SHARER)) {
-			SharerSessionState.State state =
-					SharerSessionState.State.fromValue(intState);
-			return ssFactory.build(sessionId, messageId, groupId, state,
-					contactId, forumId, d);
-		} else {
-			InviteeSessionState.State state =
-					InviteeSessionState.State.fromValue(intState);
-			return isFactory.build(sessionId, messageId, groupId, state,
-					contactId, forumId, d);
-		}
-	}
-
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = new BdfDictionary();
-		d.put(SESSION_ID, getSessionId());
-		d.put(STORAGE_ID, getStorageId());
-		d.put(GROUP_ID, getContactGroupId());
-		d.put(CONTACT_ID, getContactId().getInt());
-		d.put(SHAREABLE_ID, getShareableId());
-
-		return d;
-	}
-
-	public SessionId getSessionId() {
-		return sessionId;
-	}
-
-	public MessageId getStorageId() {
-		return storageId;
-	}
-
-	public GroupId getContactGroupId() {
-		return groupId;
-	}
-
-	public ContactId getContactId() {
-		return contactId;
-	}
-
-	public GroupId getShareableId() {
-		return shareableId;
-	}
-
-	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/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java
index 67b1a37614252402e66bf4488521b73b7187094a..5f725591f1f6127c8bbd645b309a61c782c44fdb 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingIntegrationTest.java
@@ -4,7 +4,6 @@ import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.db.DbException;
-import org.briarproject.bramble.api.db.NoSuchGroupException;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventListener;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -17,6 +16,7 @@ import org.briarproject.briar.api.blog.BlogManager;
 import org.briarproject.briar.api.blog.BlogSharingManager;
 import org.briarproject.briar.api.blog.event.BlogInvitationRequestReceivedEvent;
 import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
+import org.briarproject.briar.api.client.ProtocolStateException;
 import org.briarproject.briar.api.sharing.InvitationMessage;
 import org.briarproject.briar.test.BriarIntegrationTest;
 import org.briarproject.briar.test.BriarIntegrationTestComponent;
@@ -35,7 +35,6 @@ import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 public class BlogSharingIntegrationTest
 		extends BriarIntegrationTest<BriarIntegrationTestComponent> {
@@ -92,7 +91,7 @@ public class BlogSharingIntegrationTest
 		injectEagerSingletons(c2);
 	}
 
-	@Test
+	@Test(expected = ProtocolStateException.class)
 	public void testPersonalBlogCannotBeSharedWithOwner() throws Exception {
 		listenToEvents(true);
 
@@ -109,11 +108,6 @@ public class BlogSharingIntegrationTest
 		blogSharingManager0
 				.sendInvitation(blog1.getId(), contactId1From0, "Hi!",
 						clock.currentTimeMillis());
-
-		// sync invitation
-		sync0To1(1, false);
-		// make sure the invitee ignored the request for their own blog
-		assertFalse(listener1.requestReceived);
 	}
 
 	@Test
@@ -293,17 +287,11 @@ public class BlogSharingIntegrationTest
 		assertFalse(blogSharingManager0.getSharedWith(blog2.getId())
 				.contains(contact1From0));
 		// invitee no longer has blog shared by sharer
-		try {
-			blogSharingManager1.getSharedWith(blog2.getId());
-			fail();
-		} catch (NoSuchGroupException e) {
-			// expected
-		}
-		// blog can be shared again
+		assertEquals(0,
+				blogSharingManager1.getSharedWith(blog2.getId()).size());
+		// blog can be shared again by sharer
 		assertTrue(
 				blogSharingManager0.canBeShared(blog2.getId(), contact1From0));
-		assertTrue(
-				blogSharingManager1.canBeShared(blog2.getId(), contact0From1));
 	}
 
 	@Test
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c67360d648b933c8a543e45ea640893f8621a9db
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java
@@ -0,0 +1,185 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.BdfMessageContext;
+import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorId;
+import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.briar.api.blog.Blog;
+import org.jmock.Expectations;
+import org.junit.Test;
+
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_NAME_LENGTH;
+import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
+import static org.briarproject.briar.sharing.MessageType.INVITE;
+
+public class BlogSharingValidatorTest extends SharingValidatorTest {
+
+	private final AuthorId authorId = new AuthorId(getRandomId());
+	private final String authorName = TestUtils.getRandomString(42);
+	private final byte[] publicKey =
+			TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+	private final Author author = new Author(authorId, authorName, publicKey);
+	private final Blog blog = new Blog(group, author);
+	private final BdfList descriptor = BdfList.of(authorName, publicKey);
+	private final String content =
+			TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
+
+	@Override
+	SharingValidator getValidator() {
+		return new BlogSharingValidator(messageEncoder, clientHelper,
+				metadataEncoder, clock, blogFactory, authorFactory);
+	}
+
+	@Test
+	public void testAcceptsInvitationWithContent() throws Exception {
+		expectCreateBlog(authorName, publicKey);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
+						content));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test
+	public void testAcceptsInvitationWithNullContent() throws Exception {
+		expectCreateBlog(authorName, publicKey);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test
+	public void testAcceptsInvitationWithNullPreviousMsgId() throws Exception {
+		expectCreateBlog(authorName, publicKey);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), null, descriptor, null));
+		assertExpectedContext(messageContext, null);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNullBlogName() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(null, publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNonStringBlogName() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(123, publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooShortBlogName() throws Exception {
+		BdfList invalidDescriptor = BdfList.of("", publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test
+	public void testAcceptsMinLengthBlogName() throws Exception {
+		String shortBlogName = TestUtils.getRandomString(1);
+		BdfList validDescriptor = BdfList.of(shortBlogName, publicKey);
+		expectCreateBlog(shortBlogName, publicKey);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
+						null));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongBlogName() throws Exception {
+		String invalidBlogName =
+				TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1);
+		BdfList invalidDescriptor = BdfList.of(invalidBlogName, publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNullPublicKey() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(authorName, null);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNonRawPublicKey() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(authorName, 123);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongPublicKey() throws Exception {
+		byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
+		BdfList invalidDescriptor = BdfList.of(authorName, invalidKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
+						null));
+	}
+
+	@Test
+	public void testAcceptsMinLengthPublicKey() throws Exception {
+		byte[] key = TestUtils.getRandomBytes(1);
+		BdfList validDescriptor = BdfList.of(authorName, key);
+
+		expectCreateBlog(authorName, key);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
+						null));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNonStringContent() throws Exception {
+		expectCreateBlog(authorName, publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
+						123));
+	}
+
+	@Test
+	public void testAcceptsMinLengthContent() throws Exception {
+		expectCreateBlog(authorName, publicKey);
+		expectEncodeMetadata(INVITE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, "1"));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongContent() throws Exception {
+		String invalidContent =
+				TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
+		expectCreateBlog(authorName, publicKey);
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
+						invalidContent));
+	}
+
+	private void expectCreateBlog(final String name, final byte[] key) {
+		context.checking(new Expectations() {{
+			oneOf(authorFactory).createAuthor(name, key);
+			will(returnValue(author));
+			oneOf(blogFactory).createBlog(author);
+			will(returnValue(blog));
+		}});
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
index c7d2d98f26c03346953564585671a41d0f7f5c4a..038da56a94a71f88efda2aa9c067adc9b6512d63 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
@@ -1,45 +1,20 @@
 package org.briarproject.briar.sharing;
 
 import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.UniqueId;
 import org.briarproject.bramble.api.client.BdfMessageContext;
-import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.test.ValidatorTestCase;
 import org.briarproject.briar.api.forum.Forum;
-import org.briarproject.briar.api.forum.ForumFactory;
 import org.jmock.Expectations;
 import org.junit.Test;
 
-import java.util.Collection;
-
-import javax.annotation.Nullable;
-
-import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
-import static org.briarproject.briar.sharing.MessageType.ABORT;
-import static org.briarproject.briar.sharing.MessageType.ACCEPT;
-import static org.briarproject.briar.sharing.MessageType.DECLINE;
 import static org.briarproject.briar.sharing.MessageType.INVITE;
-import static org.briarproject.briar.sharing.MessageType.LEAVE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class ForumSharingValidatorTest extends ValidatorTestCase {
 
-	private final MessageEncoder messageEncoder =
-			context.mock(MessageEncoder.class);
-	private final ForumFactory forumFactory = context.mock(ForumFactory.class);
-	private final ForumSharingValidator v =
-			new ForumSharingValidator(messageEncoder, clientHelper,
-					metadataEncoder, clock, forumFactory);
+public class ForumSharingValidatorTest extends SharingValidatorTest {
 
-	private final MessageId previousMsgId = new MessageId(getRandomId());
 	private final String forumName =
 			TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH);
 	private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH);
@@ -47,8 +22,12 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 	private final BdfList descriptor = BdfList.of(forumName, salt);
 	private final String content =
 			TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
-	private final BdfDictionary meta =
-			BdfDictionary.of(new BdfEntry("meta", "data"));
+
+	@Override
+	SharingValidator getValidator() {
+		return new ForumSharingValidator(messageEncoder, clientHelper,
+				metadataEncoder, clock, forumFactory);
+	}
 
 	@Test
 	public void testAcceptsInvitationWithContent() throws Exception {
@@ -78,107 +57,6 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 		assertExpectedContext(messageContext, null);
 	}
 
-	@Test
-	public void testAcceptsAccept() throws Exception {
-		expectEncodeMetadata(ACCEPT);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(ACCEPT.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
-	@Test
-	public void testAcceptsDecline() throws Exception {
-		expectEncodeMetadata(DECLINE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(DECLINE.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
-	@Test
-	public void testAcceptsLeave() throws Exception {
-		expectEncodeMetadata(LEAVE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(LEAVE.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
-	@Test
-	public void testAcceptsAbort() throws Exception {
-		expectEncodeMetadata(ABORT);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNullMessageType() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(null, groupId, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNonLongMessageType() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of("", groupId, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsInvalidMessageType() throws Exception {
-		int invalidMessageType = ABORT.getValue() + 1;
-		v.validateMessage(message, group,
-				BdfList.of(invalidMessageType, groupId, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNullSessionId() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), null, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNonRawSessionId() throws Exception {
-		v.validateMessage(message, group, BdfList.of(ABORT.getValue(), 123));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooShortSessionId() throws Exception {
-		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
-		v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongSessionId() throws Exception {
-		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
-		v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooShortBodyForAbort() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), groupId));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongBodyForAbort() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(ABORT.getValue(), groupId, previousMsgId, 123));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooShortBodyForInvitation() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, descriptor));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongBodyForInvitation() throws Exception {
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null,
-						123));
-	}
-
 	@Test(expected = FormatException.class)
 	public void testRejectsNullForumName() throws Exception {
 		BdfList invalidDescriptor = BdfList.of(null, salt);
@@ -293,25 +171,4 @@ public class ForumSharingValidatorTest extends ValidatorTestCase {
 		}});
 	}
 
-	private void expectEncodeMetadata(final MessageType type) {
-		context.checking(new Expectations() {{
-			oneOf(messageEncoder)
-					.encodeMetadata(type, groupId, timestamp, false, false,
-							false, false);
-			will(returnValue(meta));
-		}});
-	}
-
-	private void assertExpectedContext(BdfMessageContext messageContext,
-			@Nullable MessageId previousMsgId) throws FormatException {
-		Collection<MessageId> dependencies = messageContext.getDependencies();
-		if (previousMsgId == null) {
-			assertTrue(dependencies.isEmpty());
-		} else {
-			assertEquals(1, dependencies.size());
-			assertTrue(dependencies.contains(previousMsgId));
-		}
-		assertEquals(meta, messageContext.getDictionary());
-	}
-
 }
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8e688ce1f0801c23cc74d8216b09ffb3a1d9cfc
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java
@@ -0,0 +1,167 @@
+package org.briarproject.briar.sharing;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.UniqueId;
+import org.briarproject.bramble.api.client.BdfMessageContext;
+import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.data.BdfEntry;
+import org.briarproject.bramble.api.data.BdfList;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.bramble.test.ValidatorTestCase;
+import org.briarproject.briar.api.blog.BlogFactory;
+import org.briarproject.briar.api.forum.ForumFactory;
+import org.jmock.Expectations;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.briar.sharing.MessageType.ABORT;
+import static org.briarproject.briar.sharing.MessageType.ACCEPT;
+import static org.briarproject.briar.sharing.MessageType.DECLINE;
+import static org.briarproject.briar.sharing.MessageType.INVITE;
+import static org.briarproject.briar.sharing.MessageType.LEAVE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public abstract class SharingValidatorTest extends ValidatorTestCase {
+
+	protected final MessageEncoder messageEncoder =
+			context.mock(MessageEncoder.class);
+	protected final ForumFactory forumFactory =
+			context.mock(ForumFactory.class);
+	protected final BlogFactory blogFactory = context.mock(BlogFactory.class);
+	protected final SharingValidator v = getValidator();
+
+	protected final MessageId previousMsgId = new MessageId(getRandomId());
+	private final BdfDictionary meta =
+			BdfDictionary.of(new BdfEntry("meta", "data"));
+
+	abstract SharingValidator getValidator();
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooShortBodyForInvitation() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongBodyForInvitation() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null,
+						123));
+	}
+
+	@Test
+	public void testAcceptsAccept() throws Exception {
+		expectEncodeMetadata(ACCEPT);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(ACCEPT.getValue(), groupId, previousMsgId));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test
+	public void testAcceptsDecline() throws Exception {
+		expectEncodeMetadata(DECLINE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(DECLINE.getValue(), groupId, previousMsgId));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test
+	public void testAcceptsLeave() throws Exception {
+		expectEncodeMetadata(LEAVE);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(LEAVE.getValue(), groupId, previousMsgId));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test
+	public void testAcceptsAbort() throws Exception {
+		expectEncodeMetadata(ABORT);
+		BdfMessageContext messageContext = v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), groupId, previousMsgId));
+		assertExpectedContext(messageContext, previousMsgId);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNullMessageType() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(null, groupId, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNonLongMessageType() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of("", groupId, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsInvalidMessageType() throws Exception {
+		int invalidMessageType = ABORT.getValue() + 1;
+		v.validateMessage(message, group,
+				BdfList.of(invalidMessageType, groupId, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNullSessionId() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), null, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsNonRawSessionId() throws Exception {
+		v.validateMessage(message, group, BdfList.of(ABORT.getValue(), 123));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooShortSessionId() throws Exception {
+		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
+		v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongSessionId() throws Exception {
+		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
+		v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooShortBodyForAbort() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), groupId));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongBodyForAbort() throws Exception {
+		v.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), groupId, previousMsgId, 123));
+	}
+
+	protected void expectEncodeMetadata(final MessageType type) {
+		context.checking(new Expectations() {{
+			oneOf(messageEncoder)
+					.encodeMetadata(type, groupId, timestamp, false, false,
+							false, false);
+			will(returnValue(meta));
+		}});
+	}
+
+	protected void assertExpectedContext(BdfMessageContext messageContext,
+			@Nullable MessageId previousMsgId) throws FormatException {
+		Collection<MessageId> dependencies = messageContext.getDependencies();
+		if (previousMsgId == null) {
+			assertTrue(dependencies.isEmpty());
+		} else {
+			assertEquals(1, dependencies.size());
+			assertTrue(dependencies.contains(previousMsgId));
+		}
+		assertEquals(meta, messageContext.getDictionary());
+	}
+
+}