diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
index 1714a49ea9b6861671d83be2e1313cbbe423262c..a9e21a6b0f9364938fad3bc829a0b918f877e824 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
@@ -31,6 +31,7 @@ import org.briarproject.crypto.CryptoModule;
 import org.briarproject.forum.ForumModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.transport.TransportModule;
 import org.briarproject.util.StringUtils;
@@ -188,7 +189,7 @@ public class ForumManagerTest {
 
 		// share forum
 		GroupId g = forum0.getId();
-		forumSharingManager0.sendForumInvitation(g, contactId1, null);
+		forumSharingManager0.sendInvitation(g, contactId1, null);
 		sync0To1();
 		deliveryWaiter.await(TIMEOUT, 1);
 		Contact c0 = contactManager1.getContact(contactId0);
@@ -218,7 +219,7 @@ public class ForumManagerTest {
 
 		// share forum
 		GroupId g = forum0.getId();
-		forumSharingManager0.sendForumInvitation(g, contactId1, null);
+		forumSharingManager0.sendInvitation(g, contactId1, null);
 		sync0To1();
 		deliveryWaiter.await(TIMEOUT, 1);
 		Contact c0 = contactManager1.getContact(contactId0);
@@ -259,7 +260,7 @@ public class ForumManagerTest {
 
 		// share forum
 		GroupId g = forum0.getId();
-		forumSharingManager0.sendForumInvitation(g, contactId1, null);
+		forumSharingManager0.sendInvitation(g, contactId1, null);
 		sync0To1();
 		deliveryWaiter.await(TIMEOUT, 1);
 		Contact c0 = contactManager1.getContact(contactId0);
@@ -270,7 +271,7 @@ public class ForumManagerTest {
 		// share a second forum
 		Forum forum1 = forumManager0.addForum("Test Forum1");
 		GroupId g1 = forum1.getId();
-		forumSharingManager0.sendForumInvitation(g1, contactId1, null);
+		forumSharingManager0.sendInvitation(g1, contactId1, null);
 		sync0To1();
 		deliveryWaiter.await(TIMEOUT, 1);
 		forumSharingManager1.respondToInvitation(forum1, c0, true);
@@ -426,6 +427,7 @@ public class ForumManagerTest {
 		component.inject(new CryptoModule.EagerSingletons());
 		component.inject(new ContactModule.EagerSingletons());
 		component.inject(new TransportModule.EagerSingletons());
+		component.inject(new SharingModule.EagerSingletons());
 		component.inject(new SyncModule.EagerSingletons());
 		component.inject(new PropertiesModule.EagerSingletons());
 	}
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java
index 8a9e5fc6db41a653e6e9737a39874cb89dce6b4c..7e74d841b01a425caec7deac4ce9235d3c77767a 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java
@@ -17,6 +17,7 @@ import org.briarproject.forum.ForumModule;
 import org.briarproject.identity.IdentityModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.system.SystemModule;
 import org.briarproject.transport.TransportModule;
@@ -40,6 +41,7 @@ import dagger.Component;
 		IdentityModule.class,
 		LifecycleModule.class,
 		PropertiesModule.class,
+		SharingModule.class,
 		SyncModule.class,
 		SystemModule.class,
 		TransportModule.class
@@ -58,6 +60,8 @@ public interface ForumManagerTestComponent {
 
 	void inject(PropertiesModule.EagerSingletons init);
 
+	void inject(SharingModule.EagerSingletons init);
+
 	void inject(SyncModule.EagerSingletons init);
 
 	void inject(TransportModule.EagerSingletons init);
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
index 3d55604e30852e8986f33c80d1c0d2532dcf9b4a..522ca424ee2d26af423b0a5743f348d5e98380ec 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
@@ -39,6 +39,7 @@ import org.briarproject.crypto.CryptoModule;
 import org.briarproject.forum.ForumModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.transport.TransportModule;
 import org.junit.After;
@@ -61,8 +62,8 @@ import javax.inject.Inject;
 
 import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
 import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.junit.Assert.assertEquals;
@@ -159,7 +160,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// sync first request message
 			syncToInvitee();
@@ -172,13 +173,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertTrue(listener0.responseReceived);
 
 			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getAvailableForums().size());
+			assertEquals(0, forumSharingManager0.getAvailable().size());
 			assertEquals(1, forumManager1.getForums().size());
 
 			// invitee has one invitation message from sharer
 			List<ForumInvitationMessage> list =
 					new ArrayList<>(forumSharingManager1
-							.getForumInvitationMessages(contactId0));
+							.getInvitationMessages(contactId0));
 			assertEquals(1, list.size());
 			// check other things are alright with the forum message
 			ForumInvitationMessage invitation = list.get(0);
@@ -188,7 +189,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertEquals("Hi!", invitation.getMessage());
 			// sharer has own invitation message
 			assertEquals(1,
-					forumSharingManager0.getForumInvitationMessages(contactId1)
+					forumSharingManager0.getInvitationMessages(contactId1)
 							.size());
 			// forum can not be shared again
 			Contact c1 = contactManager0.getContact(contactId1);
@@ -209,7 +210,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, null);
+					.sendInvitation(forum0.getId(), contactId1, null);
 
 			// sync first request message
 			syncToInvitee();
@@ -222,15 +223,15 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertTrue(listener0.responseReceived);
 
 			// forum was not added
-			assertEquals(0, forumSharingManager0.getAvailableForums().size());
+			assertEquals(0, forumSharingManager0.getAvailable().size());
 			assertEquals(0, forumManager1.getForums().size());
 			// forum is no longer available to invitee who declined
-			assertEquals(0, forumSharingManager1.getAvailableForums().size());
+			assertEquals(0, forumSharingManager1.getAvailable().size());
 
 			// invitee has one invitation message from sharer
 			List<ForumInvitationMessage> list =
 					new ArrayList<>(forumSharingManager1
-							.getForumInvitationMessages(contactId0));
+							.getInvitationMessages(contactId0));
 			assertEquals(1, list.size());
 			// check other things are alright with the forum message
 			ForumInvitationMessage invitation = list.get(0);
@@ -240,7 +241,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertEquals(null, invitation.getMessage());
 			// sharer has own invitation message
 			assertEquals(1,
-					forumSharingManager0.getForumInvitationMessages(contactId1)
+					forumSharingManager0.getInvitationMessages(contactId1)
 							.size());
 			// forum can be shared again
 			Contact c1 = contactManager0.getContact(contactId1);
@@ -259,7 +260,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// sync first request message
 			syncToInvitee();
@@ -272,7 +273,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertTrue(listener0.responseReceived);
 
 			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getAvailableForums().size());
+			assertEquals(0, forumSharingManager0.getAvailable().size());
 			assertEquals(1, forumManager1.getForums().size());
 			assertTrue(forumManager1.getForums().contains(forum0));
 
@@ -292,7 +293,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			syncToSharer();
 
 			// forum is gone
-			assertEquals(0, forumSharingManager0.getAvailableForums().size());
+			assertEquals(0, forumSharingManager0.getAvailable().size());
 			assertEquals(0, forumManager1.getForums().size());
 
 			// sharer no longer shares forum with invitee
@@ -319,7 +320,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, null);
+					.sendInvitation(forum0.getId(), contactId1, null);
 
 			// sync first request message
 			syncToInvitee();
@@ -332,7 +333,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertTrue(listener0.responseReceived);
 
 			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getAvailableForums().size());
+			assertEquals(0, forumSharingManager0.getAvailable().size());
 			assertEquals(1, forumManager1.getForums().size());
 			assertTrue(forumManager1.getForums().contains(forum0));
 
@@ -378,7 +379,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, null);
+					.sendInvitation(forum0.getId(), contactId1, null);
 
 			// sharer un-subscribes from forum
 			forumManager0.removeForum(forum0);
@@ -392,7 +393,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertTrue(listener1.requestReceived);
 
 			// invitee has no forums available
-			assertEquals(0, forumSharingManager1.getAvailableForums().size());
+			assertEquals(0, forumSharingManager1.getAvailable().size());
 		} finally {
 			stopLifecycles();
 		}
@@ -407,7 +408,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// sync first request message
 			syncToInvitee();
@@ -428,7 +429,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			// get SessionId from invitation
 			List<ForumInvitationMessage> list = new ArrayList<>(
 					forumSharingManager1
-							.getForumInvitationMessages(contactId0));
+							.getInvitationMessages(contactId0));
 			assertEquals(1, list.size());
 			ForumInvitationMessage msg = list.get(0);
 			SessionId sessionId = msg.getSessionId();
@@ -475,7 +476,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// sync first request message
 			syncToInvitee();
@@ -491,7 +492,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			assertEquals(1, forumManager1.getForums().size());
 
 			// invitee now shares same forum back
-			forumSharingManager1.sendForumInvitation(forum0.getId(),
+			forumSharingManager1.sendInvitation(forum0.getId(),
 					contactId0,
 					"I am re-sharing this forum with you.");
 
@@ -501,7 +502,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			// make sure that no new request was received
 			assertFalse(listener0.requestReceived);
 			assertEquals(1,
-					forumSharingManager0.getForumInvitationMessages(contactId1)
+					forumSharingManager0.getInvitationMessages(contactId1)
 							.size());
 		} finally {
 			stopLifecycles();
@@ -524,10 +525,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// invitee now shares same forum back
-			forumSharingManager1.sendForumInvitation(forum0.getId(),
+			forumSharingManager1.sendInvitation(forum0.getId(),
 					contactId0, "I am re-sharing this forum with you.");
 
 			// find out who should be Alice, because of random keys
@@ -555,9 +556,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 				assertTrue(listener0.responseReceived);
 
 				assertEquals(1, forumSharingManager0
-						.getForumInvitationMessages(contactId1).size());
+						.getInvitationMessages(contactId1).size());
 				assertEquals(2, forumSharingManager1
-						.getForumInvitationMessages(contactId0).size());
+						.getInvitationMessages(contactId0).size());
 			} else {
 				eventWaiter.await(TIMEOUT, 1);
 				assertTrue(listener0.requestReceived);
@@ -568,9 +569,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 				assertTrue(listener1.responseReceived);
 
 				assertEquals(2, forumSharingManager0
-						.getForumInvitationMessages(contactId1).size());
+						.getInvitationMessages(contactId1).size());
 				assertEquals(1, forumSharingManager1
-						.getForumInvitationMessages(contactId0).size());
+						.getInvitationMessages(contactId0).size());
 			}
 		} finally {
 			stopLifecycles();
@@ -586,7 +587,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 
 			// sync first request message
 			syncToInvitee();
@@ -606,7 +607,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			// remember SessionId from invitation
 			List<ForumInvitationMessage> list = new ArrayList<>(
 					forumSharingManager1
-							.getForumInvitationMessages(contactId0));
+							.getInvitationMessages(contactId0));
 			assertEquals(1, list.size());
 			ForumInvitationMessage msg = list.get(0);
 			SessionId sessionId = msg.getSessionId();
@@ -687,20 +688,20 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 			// send invitation
 			forumSharingManager0
-					.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
+					.sendInvitation(forum0.getId(), contactId1, "Hi!");
 			// sync first request message
 			syncToInvitee();
 
 			// second sharer sends invitation for same forum
 			forumSharingManager2
-					.sendForumInvitation(forum0.getId(), contactId1, null);
+					.sendInvitation(forum0.getId(), contactId1, null);
 			// sync second request message
 			deliverMessage(sync2, contactId2, sync1, contactId1,
 					"Sharer2 to Invitee");
 
 			// make sure we have only one forum available
 			Collection<Forum> forums =
-					forumSharingManager1.getAvailableForums();
+					forumSharingManager1.getAvailable();
 			assertEquals(1, forums.size());
 
 			// make sure both sharers actually share the forum
@@ -958,6 +959,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 		component.inject(new CryptoModule.EagerSingletons());
 		component.inject(new ContactModule.EagerSingletons());
 		component.inject(new TransportModule.EagerSingletons());
+		component.inject(new SharingModule.EagerSingletons());
 		component.inject(new SyncModule.EagerSingletons());
 		component.inject(new PropertiesModule.EagerSingletons());
 	}
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
index 55f3d8de82b117a6b2d415f03458c70210a8aa75..8e3ecdf9f6b5ce55cf19f5ef51a135f65bda6eea 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
@@ -21,6 +21,7 @@ import org.briarproject.forum.ForumModule;
 import org.briarproject.identity.IdentityModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.system.SystemModule;
 import org.briarproject.transport.TransportModule;
@@ -44,6 +45,7 @@ import dagger.Component;
 		IdentityModule.class,
 		LifecycleModule.class,
 		PropertiesModule.class,
+		SharingModule.class,
 		SyncModule.class,
 		SystemModule.class,
 		TransportModule.class
@@ -62,6 +64,8 @@ public interface ForumSharingIntegrationTestComponent {
 
 	void inject(PropertiesModule.EagerSingletons init);
 
+	void inject(SharingModule.EagerSingletons init);
+
 	void inject(SyncModule.EagerSingletons init);
 
 	void inject(TransportModule.EagerSingletons init);
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index d0514a17229dc5458b675b6eeb5172a8b3387f32..7b71d84f040b0086010ce833d0d38a082d422a83 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -352,7 +352,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
 
 		now = System.currentTimeMillis();
 		Collection<ForumInvitationMessage> invitations =
-				forumSharingManager.getForumInvitationMessages(id);
+				forumSharingManager.getInvitationMessages(id);
 		for (ForumInvitationMessage i : invitations) {
 			messages.add(ConversationItem.from(i));
 		}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index b1507615aedf709d828438b94a05ab584553592c..116d2a6354b6a3c18f297926f707487552c5d386 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -316,7 +316,7 @@ public class ConversationActivity extends BriarActivity
 									.getIntroductionMessages(contactId);
 					Collection<ForumInvitationMessage> invitations =
 							forumSharingManager
-									.getForumInvitationMessages(contactId);
+									.getInvitationMessages(contactId);
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading headers took " + duration + " ms");
diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java
index 15e919c90283e040dbb0825a7120a4af9999080c..f7034475cfe2b89f043f887a0a3f11030d8a714f 100644
--- a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java
@@ -82,7 +82,7 @@ public class AvailableForumsActivity extends BriarActivity
 				try {
 					Collection<ForumContacts> available = new ArrayList<>();
 					long now = System.currentTimeMillis();
-					for (Forum f : forumSharingManager.getAvailableForums()) {
+					for (Forum f : forumSharingManager.getAvailable()) {
 						try {
 							Collection<Contact> c =
 									forumSharingManager.getSharedBy(f.getId());
diff --git a/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java
index 8cefe834f75e42d1b7f107b4f052c1054301a0c2..313231ebcbdbc8f74aabae4d41ac4191e9b2f61d 100644
--- a/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java
@@ -38,7 +38,7 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
 import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
 
 public class ContactSelectorFragment extends BaseFragment implements
 		BaseContactListAdapter.OnItemClickListener {
diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
index afe61864a7e6cd7b725ad9e4ad395f81dbde2f04..ea33d2b3791ef52c646f3dec39acbef7929a679f 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
@@ -177,7 +177,7 @@ public class ForumListFragment extends BaseEventFragment implements
 				try {
 					long now = System.currentTimeMillis();
 					int available =
-							forumSharingManager.getAvailableForums().size();
+							forumSharingManager.getAvailable().size();
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
 						LOG.info("Loading available took " + duration + " ms");
diff --git a/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java b/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java
index d88c33d548a6a2bf67bc874e8266fc1840a0596d..cf2b6e16062c4ffb5990fe27b6d0baa28e5f1afa 100644
--- a/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java
@@ -28,7 +28,7 @@ import static android.widget.Toast.LENGTH_SHORT;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
 import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
 
 public class ShareForumMessageFragment extends BaseFragment {
 
@@ -136,7 +136,7 @@ public class ShareForumMessageFragment extends BaseFragment {
 			public void run() {
 				try {
 					for (ContactId c : contacts) {
-						forumSharingManager.sendForumInvitation(groupId, c,
+						forumSharingManager.sendInvitation(groupId, c,
 								msg);
 					}
 				} catch (DbException e) {
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
index 60f06a641b5591ed79c35d32e785d3265358cff6..738e012a3ef2872f1c341aacc7e5a6a68d5a7da5 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
@@ -22,6 +22,12 @@ public interface BlogConstants {
 	/** The maximum length of a blog post's body in bytes. */
 	int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
+	/* Blog Sharing Constants */
+	String BLOG_TITLE = "blogTitle";
+	String BLOG_DESC = "blogDescription";
+	String BLOG_AUTHOR_NAME = "blogAuthorName";
+	String BLOG_PUBLIC_KEY = "blogPublicKey";
+
 	// Metadata keys
 	String KEY_DESCRIPTION = "description";
 	String KEY_TITLE = "title";
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogInvitationMessage.java b/briar-api/src/org/briarproject/api/blogs/BlogInvitationMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..9306da5726c5a74cea45a1524f573949c6f686df
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogInvitationMessage.java
@@ -0,0 +1,26 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.sharing.InvitationMessage;
+import org.briarproject.api.sync.MessageId;
+
+public class BlogInvitationMessage extends InvitationMessage {
+
+	private final String blogTitle;
+
+	public BlogInvitationMessage(MessageId id, SessionId sessionId,
+			ContactId contactId, String blogTitle, String message,
+			boolean available, long time, boolean local, boolean sent,
+			boolean seen, boolean read) {
+
+		super(id, sessionId, contactId, message, available, time, local, sent,
+				seen, read);
+		this.blogTitle = blogTitle;
+	}
+
+	public String getBlogTitle() {
+		return blogTitle;
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec9f23e49df4910fc2bdc5935fd98b4e9eed4cbd
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java
@@ -0,0 +1,60 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sharing.SharingManager;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface BlogSharingManager
+		extends SharingManager<Blog, BlogInvitationMessage> {
+
+	/**
+	 * Returns the unique ID of the blog sharing client.
+	 */
+	ClientId getClientId();
+
+	/**
+	 * Sends an invitation to share the given blog with the given contact
+	 * and sends an optional message along with it.
+	 */
+	void sendInvitation(GroupId groupId, ContactId contactId,
+			String message) throws DbException;
+
+	/**
+	 * Responds to a pending blog invitation
+	 */
+	void respondToInvitation(Blog b, Contact c, boolean accept)
+			throws DbException;
+
+	/**
+	 * Returns all blogs sharing messages sent by the Contact
+	 * identified by contactId.
+	 */
+	Collection<BlogInvitationMessage> getInvitationMessages(
+			ContactId contactId) throws DbException;
+
+	/**
+	 * Returns all blogs to which the user could subscribe.
+	 */
+	Collection<Blog> getAvailable() throws DbException;
+
+	/**
+	 * Returns all contacts who are sharing the given blog with us.
+	 */
+	Collection<Contact> getSharedBy(GroupId g) throws DbException;
+
+	/**
+	 * Returns the IDs of all contacts with whom the given blog is shared.
+	 */
+	Collection<Contact> getSharedWith(GroupId g) throws DbException;
+
+	/**
+	 * Returns true if the blog not already shared and no invitation is open
+	 */
+	boolean canBeShared(GroupId g, Contact c) throws DbException;
+
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogSharingMessage.java b/briar-api/src/org/briarproject/api/blogs/BlogSharingMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..105b0d2a3f981c27db33f36fbc072b296f93689c
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogSharingMessage.java
@@ -0,0 +1,89 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.sharing.SharingMessage.Invitation;
+import org.briarproject.api.sync.GroupId;
+
+import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
+import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+
+public interface BlogSharingMessage {
+
+	class BlogInvitation extends Invitation {
+
+		private final String blogTitle;
+		private final String blogDesc;
+		private final String blogAuthorName;
+		private final byte[] blogPublicKey;
+
+		public BlogInvitation(GroupId groupId, SessionId sessionId,
+				String blogTitle, String blogDesc, String blogAuthorName,
+				byte[] blogPublicKey, String message) {
+
+			super(groupId, sessionId, message);
+
+			this.blogTitle = blogTitle;
+			this.blogDesc = blogDesc;
+			this.blogAuthorName = blogAuthorName;
+			this.blogPublicKey = blogPublicKey;
+		}
+
+		@Override
+		public BdfList toBdfList() {
+			BdfList list = super.toBdfList();
+			list.add(blogTitle);
+			list.add(blogDesc);
+			list.add(BdfList.of(blogAuthorName, blogPublicKey));
+			if (message != null) list.add(message);
+			return list;
+		}
+
+		@Override
+		public BdfDictionary toBdfDictionary() {
+			BdfDictionary d = toBdfDictionaryHelper();
+			d.put(BLOG_TITLE, blogTitle);
+			d.put(BLOG_DESC, blogDesc);
+			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 blogTitle = d.getString(BLOG_TITLE);
+			String blogDesc = d.getString(BLOG_DESC);
+			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
+			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
+			String message = d.getOptionalString(INVITATION_MSG);
+
+			return new BlogInvitation(groupId, sessionId, blogTitle,
+					blogDesc, blogAuthorName, blogPublicKey, message);
+		}
+
+		public String getBlogTitle() {
+			return blogTitle;
+		}
+
+		public String getBlogDesc() {
+			return blogDesc;
+		}
+
+		public String getBlogAuthorName() {
+			return blogAuthorName;
+		}
+
+		public byte[] getBlogPublicKey() {
+			return blogPublicKey;
+		}
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..12d8d4cc4b8dec2dd91f2cf4bf1f80322f56f421
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java
@@ -0,0 +1,18 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.contact.ContactId;
+
+public class BlogInvitationReceivedEvent extends InvitationReceivedEvent {
+
+	private final Blog blog;
+
+	public BlogInvitationReceivedEvent(Blog blog, ContactId contactId) {
+		super(contactId);
+		this.blog = blog;
+	}
+
+	public Blog getBlog() {
+		return blog;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/BlogInvitationResponseReceivedEvent.java b/briar-api/src/org/briarproject/api/event/BlogInvitationResponseReceivedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6344ad928dd3c5e14155ad7c8dd7c70c69e3f38
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/BlogInvitationResponseReceivedEvent.java
@@ -0,0 +1,18 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.contact.ContactId;
+
+public class BlogInvitationResponseReceivedEvent extends InvitationResponseReceivedEvent {
+
+	private final String blogTitle;
+
+	public BlogInvitationResponseReceivedEvent(String blogTitle,
+			ContactId contactId) {
+		super(contactId);
+		this.blogTitle = blogTitle;
+	}
+
+	public String getBlogTitle() {
+		return blogTitle;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java
index 1d8e7b5f7b61a1a88076277e48fb3b52cc34de13..63aa3bce19810f3ba450e7eeeed6799c070a881f 100644
--- a/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java
+++ b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java
@@ -4,21 +4,16 @@ import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.introduction.IntroductionRequest;
 
-public class ForumInvitationReceivedEvent extends Event {
+public class ForumInvitationReceivedEvent extends InvitationReceivedEvent {
 
 	private final Forum forum;
-	private final ContactId contactId;
 
 	public ForumInvitationReceivedEvent(Forum forum, ContactId contactId) {
+		super(contactId);
 		this.forum = forum;
-		this.contactId = contactId;
 	}
 
 	public Forum getForum() {
 		return forum;
 	}
-
-	public ContactId getContactId() {
-		return contactId;
-	}
 }
diff --git a/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java b/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java
index 1e799240386966ff59c73037c97e6cfaaaeb2e63..cb51daf0a2c05b8f97b3097a4ee7ee2eec48caf3 100644
--- a/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java
+++ b/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java
@@ -2,23 +2,17 @@ package org.briarproject.api.event;
 
 import org.briarproject.api.contact.ContactId;
 
-public class ForumInvitationResponseReceivedEvent extends Event {
+public class ForumInvitationResponseReceivedEvent extends InvitationResponseReceivedEvent {
 
 	private final String forumName;
-	private final ContactId contactId;
 
 	public ForumInvitationResponseReceivedEvent(String forumName,
 			ContactId contactId) {
-
+		super(contactId);
 		this.forumName = forumName;
-		this.contactId = contactId;
 	}
 
 	public String getForumName() {
 		return forumName;
 	}
-
-	public ContactId getContactId() {
-		return contactId;
-	}
 }
diff --git a/briar-api/src/org/briarproject/api/event/InvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/InvitationReceivedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b1b6df2d36fb18f0e41124919571cc04a5d415a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/InvitationReceivedEvent.java
@@ -0,0 +1,17 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.forum.Forum;
+
+public abstract class InvitationReceivedEvent extends Event {
+
+	private final ContactId contactId;
+
+	public InvitationReceivedEvent(ContactId contactId) {
+		this.contactId = contactId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/InvitationResponseReceivedEvent.java b/briar-api/src/org/briarproject/api/event/InvitationResponseReceivedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..df84a69656d5cbb38ff78843e37397eec676454d
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/InvitationResponseReceivedEvent.java
@@ -0,0 +1,16 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.contact.ContactId;
+
+public abstract class InvitationResponseReceivedEvent extends Event {
+
+	private final ContactId contactId;
+
+	public InvitationResponseReceivedEvent(ContactId contactId) {
+		this.contactId = contactId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/forum/Forum.java b/briar-api/src/org/briarproject/api/forum/Forum.java
index 2eeadc21664f069e32db944305a5d0756ec05561..61628250829fe871c0e7436d36efb28d3303c9b1 100644
--- a/briar-api/src/org/briarproject/api/forum/Forum.java
+++ b/briar-api/src/org/briarproject/api/forum/Forum.java
@@ -1,9 +1,10 @@
 package org.briarproject.api.forum;
 
+import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 
-public class Forum {
+public class Forum implements Shareable {
 
 	private final Group group;
 	private final String name;
diff --git a/briar-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java
index 0bbf8896bc9ac7757d1af47bb1cf28947e902cd7..9523b20cc8eb1fbdd73974a6461671acc19a08a6 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumConstants.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumConstants.java
@@ -17,37 +17,8 @@ public interface ForumConstants {
 	int MAX_FORUM_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
 	/* Forum Sharing Constants */
-	String CONTACT_ID = "contactId";
-	String GROUP_ID = "groupId";
-	String TO_BE_SHARED_BY_US = "toBeSharedByUs";
-	String SHARED_BY_US = "sharedByUs";
-	String SHARED_WITH_US = "sharedWithUs";
-	String TYPE = "type";
-	String SESSION_ID = "sessionId";
-	String STORAGE_ID = "storageId";
-	String STATE = "state";
-	String LOCAL = "local";
-	String TIME = "time";
-	String READ = "read";
-	String IS_SHARER = "isSharer";
-	String FORUM_ID = "forumId";
 	String FORUM_NAME = "forumName";
 	String FORUM_SALT = "forumSalt";
-	String INVITATION_MSG = "invitationMsg";
-	int SHARE_MSG_TYPE_INVITATION = 1;
-	int SHARE_MSG_TYPE_ACCEPT = 2;
-	int SHARE_MSG_TYPE_DECLINE = 3;
-	int SHARE_MSG_TYPE_LEAVE = 4;
-	int SHARE_MSG_TYPE_ABORT = 5;
-	String TASK = "task";
-	int TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US = 0;
-	int TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US = 1;
-	int TASK_ADD_SHARED_FORUM = 2;
-	int TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US = 3;
-	int TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US = 4;
-	int TASK_SHARE_FORUM = 5;
-	int TASK_UNSHARE_FORUM_SHARED_BY_US = 6;
-	int TASK_UNSHARE_FORUM_SHARED_WITH_US = 7;
 
 	// Database keys
 	String KEY_TIMESTAMP = "timestamp";
diff --git a/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java b/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java
index b153c27f11cb996d5b48f3ad3ebb4aa47e17475d..fd2d6b0d8c464bdbdce0644a3c03e1401774c5df 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java
@@ -3,46 +3,25 @@ package org.briarproject.api.forum;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.messaging.BaseMessage;
+import org.briarproject.api.sharing.InvitationMessage;
 import org.briarproject.api.sync.MessageId;
 
-public class ForumInvitationMessage extends BaseMessage {
+public class ForumInvitationMessage extends InvitationMessage {
 
-	private final SessionId sessionId;
-	private final ContactId contactId;
-	private final String forumName, message;
-	private final boolean available;
+	private final String forumName;
 
 	public ForumInvitationMessage(MessageId id, SessionId sessionId,
 			ContactId contactId, String forumName, String message,
 			boolean available, long time, boolean local, boolean sent,
 			boolean seen, boolean read) {
 
-		super(id, time, local, read, sent, seen);
-		this.sessionId = sessionId;
-		this.contactId = contactId;
+		super(id, sessionId, contactId, message, available, time, local, sent,
+				seen, read);
 		this.forumName = forumName;
-		this.message = message;
-		this.available = available;
-	}
-
-	public SessionId getSessionId() {
-		return sessionId;
-	}
-
-	public ContactId getContactId() {
-		return contactId;
 	}
 
 	public String getForumName() {
 		return forumName;
 	}
 
-	public String getMessage() {
-		return message;
-	}
-
-	public boolean isAvailable() {
-		return available;
-	}
-
 }
diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
index e38f13149e3b5be1da682efcd94b0c6eee6e0e00..55da0615f9134ee0d6a1c89f62a0d53fcef7321c 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java
@@ -1,15 +1,15 @@
 package org.briarproject.api.forum;
 
-import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.sharing.SharingManager;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 
 import java.util.Collection;
 
-public interface ForumSharingManager {
+public interface ForumSharingManager extends SharingManager<Forum, ForumInvitationMessage> {
 
 	/** Returns the unique ID of the forum sharing client. */
 	ClientId getClientId();
@@ -18,7 +18,7 @@ public interface ForumSharingManager {
 	 * Sends an invitation to share the given forum with the given contact
 	 * and sends an optional message along with it.
 	 */
-	void sendForumInvitation(GroupId groupId, ContactId contactId,
+	void sendInvitation(GroupId groupId, ContactId contactId,
 			String message)	throws DbException;
 
 	/**
@@ -31,11 +31,11 @@ public interface ForumSharingManager {
 	 * Returns all forum sharing messages sent by the Contact
 	 * identified by contactId.
 	 */
-	Collection<ForumInvitationMessage> getForumInvitationMessages(
+	Collection<ForumInvitationMessage> getInvitationMessages(
 			ContactId contactId) throws DbException;
 
 	/** Returns all forums to which the user could subscribe. */
-	Collection<Forum> getAvailableForums() throws DbException;
+	Collection<Forum> getAvailable() throws DbException;
 
 	/** Returns all contacts who are sharing the given forum with us. */
 	Collection<Contact> getSharedBy(GroupId g) throws DbException;
diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java b/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java
index 79a43ad6649010bdea912efbe12026f525f43203..45badea54948e5b5100263a194ed141cdd2abd5e 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumSharingMessage.java
@@ -3,89 +3,29 @@ package org.briarproject.api.forum;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
+import org.briarproject.api.sharing.SharingMessage.Invitation;
 import org.briarproject.api.sync.GroupId;
 
 import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.TYPE;
+import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
 
 public interface ForumSharingMessage {
 
-	abstract class BaseMessage {
-		private final GroupId groupId;
-		private final SessionId sessionId;
-
-		public BaseMessage(GroupId groupId, SessionId sessionId) {
-
-			this.groupId = groupId;
-			this.sessionId = sessionId;
-		}
-
-		public BdfList toBdfList() {
-			return BdfList.of(getType(), getSessionId());
-		}
-
-		public abstract BdfDictionary toBdfDictionary();
-
-		protected BdfDictionary toBdfDictionaryHelper() {
-			return BdfDictionary.of(
-					new BdfEntry(TYPE, getType()),
-					new BdfEntry(GROUP_ID, groupId),
-					new BdfEntry(SESSION_ID, sessionId)
-			);
-		}
-
-		public static BaseMessage from(GroupId groupId, BdfDictionary d)
-				throws FormatException {
-
-			long type = d.getLong(TYPE);
-
-			if (type == SHARE_MSG_TYPE_INVITATION)
-				return Invitation.from(groupId, d);
-			else
-				return SimpleMessage.from(type, groupId, d);
-		}
-
-		public abstract long getType();
-
-		public GroupId getGroupId() {
-			return groupId;
-		}
-
-		public SessionId getSessionId() {
-			return sessionId;
-		}
-	}
-
-	class Invitation extends BaseMessage {
+	class ForumInvitation extends Invitation {
 
 		private final String forumName;
 		private final byte[] forumSalt;
-		private final String message;
 
-		public Invitation(GroupId groupId, SessionId sessionId,
+		public ForumInvitation(GroupId groupId, SessionId sessionId,
 				String forumName, byte[] forumSalt, String message) {
 
-			super(groupId, sessionId);
+			super(groupId, sessionId, message);
 
 			this.forumName = forumName;
 			this.forumSalt = forumSalt;
-			this.message = message;
-		}
-
-		@Override
-		public long getType() {
-			return SHARE_MSG_TYPE_INVITATION;
 		}
 
 		@Override
@@ -106,7 +46,7 @@ public interface ForumSharingMessage {
 			return d;
 		}
 
-		public static Invitation from(GroupId groupId, BdfDictionary d)
+		public static ForumInvitation from(GroupId groupId, BdfDictionary d)
 				throws FormatException {
 
 			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
@@ -114,7 +54,7 @@ public interface ForumSharingMessage {
 			byte[] forumSalt = d.getRaw(FORUM_SALT);
 			String message = d.getOptionalString(INVITATION_MSG);
 
-			return new Invitation(groupId, sessionId, forumName, forumSalt,
+			return new ForumInvitation(groupId, sessionId, forumName, forumSalt,
 					message);
 		}
 
@@ -125,42 +65,5 @@ public interface ForumSharingMessage {
 		public byte[] getForumSalt() {
 			return forumSalt;
 		}
-
-		public String getMessage() {
-			return message;
-		}
-	}
-
-	class SimpleMessage extends BaseMessage {
-
-		private final long type;
-
-		public SimpleMessage(long type, GroupId groupId, SessionId sessionId) {
-			super(groupId, sessionId);
-			this.type = type;
-		}
-
-		@Override
-		public long getType() {
-			return type;
-		}
-
-		@Override
-		public BdfDictionary toBdfDictionary() {
-			return toBdfDictionaryHelper();
-		}
-
-		public static SimpleMessage from(long type, GroupId groupId,
-				BdfDictionary d) throws FormatException {
-
-			if (type != SHARE_MSG_TYPE_ACCEPT &&
-					type != SHARE_MSG_TYPE_DECLINE &&
-					type != SHARE_MSG_TYPE_LEAVE &&
-					type != SHARE_MSG_TYPE_ABORT) throw new FormatException();
-
-			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
-			return new SimpleMessage(type, groupId, sessionId);
-		}
 	}
-
 }
diff --git a/briar-api/src/org/briarproject/api/sharing/InvitationFactory.java b/briar-api/src/org/briarproject/api/sharing/InvitationFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f8548d38433666d25719ce8ed3a799eabe19ba9
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/InvitationFactory.java
@@ -0,0 +1,10 @@
+package org.briarproject.api.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+
+public interface InvitationFactory<I extends SharingMessage.Invitation> {
+
+	I build(GroupId groupId, BdfDictionary d) throws FormatException;
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java b/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d3c12e26188945ef59cdf7809c254c1d5b3dadc
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java
@@ -0,0 +1,43 @@
+package org.briarproject.api.sharing;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.messaging.BaseMessage;
+import org.briarproject.api.sync.MessageId;
+
+public abstract class InvitationMessage extends BaseMessage {
+
+	private final SessionId sessionId;
+	private final ContactId contactId;
+	private final String message;
+	private final boolean available;
+
+	public InvitationMessage(MessageId id, SessionId sessionId,
+			ContactId contactId, String message,
+			boolean available, long time, boolean local, boolean sent,
+			boolean seen, boolean read) {
+
+		super(id, time, local, read, sent, seen);
+		this.sessionId = sessionId;
+		this.contactId = contactId;
+		this.message = message;
+		this.available = available;
+	}
+
+	public SessionId getSessionId() {
+		return sessionId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public boolean isAvailable() {
+		return available;
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/Shareable.java b/briar-api/src/org/briarproject/api/sharing/Shareable.java
new file mode 100644
index 0000000000000000000000000000000000000000..13c11fdeeff5b39d3f775b293281bcfabc08d097
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/Shareable.java
@@ -0,0 +1,11 @@
+package org.briarproject.api.sharing;
+
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupId;
+
+public interface Shareable {
+
+	GroupId getId();
+
+	Group getGroup();
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..952bfd6602b2c3956c287c17bad66abe1cb30fe0
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java
@@ -0,0 +1,38 @@
+package org.briarproject.api.sharing;
+
+public interface SharingConstants {
+
+	/** The length of a sharing session's random salt in bytes. */
+	int SHARING_SALT_LENGTH = 32;
+
+	String CONTACT_ID = "contactId";
+	String GROUP_ID = "groupId";
+	String TO_BE_SHARED_BY_US = "toBeSharedByUs";
+	String SHARED_BY_US = "sharedByUs";
+	String SHARED_WITH_US = "sharedWithUs";
+	String TYPE = "type";
+	String SESSION_ID = "sessionId";
+	String STORAGE_ID = "storageId";
+	String STATE = "state";
+	String LOCAL = "local";
+	String TIME = "time";
+	String READ = "read";
+	String IS_SHARER = "isSharer";
+	String SHAREABLE_ID = "shareableId";
+	String INVITATION_MSG = "invitationMsg";
+	int SHARE_MSG_TYPE_INVITATION = 1;
+	int SHARE_MSG_TYPE_ACCEPT = 2;
+	int SHARE_MSG_TYPE_DECLINE = 3;
+	int SHARE_MSG_TYPE_LEAVE = 4;
+	int SHARE_MSG_TYPE_ABORT = 5;
+	String TASK = "task";
+	int TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US = 0;
+	int TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US = 1;
+	int TASK_ADD_SHARED_SHAREABLE = 2;
+	int TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US = 3;
+	int TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US = 4;
+	int TASK_SHARE_SHAREABLE = 5;
+	int TASK_UNSHARE_SHAREABLE_SHARED_BY_US = 6;
+	int TASK_UNSHARE_SHAREABLE_SHARED_WITH_US = 7;
+
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c5db480b7058bf63d4730a8f6d9c406c119fd49
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
@@ -0,0 +1,50 @@
+package org.briarproject.api.sharing;
+
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.forum.Forum;
+import org.briarproject.api.forum.ForumInvitationMessage;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+
+import java.util.Collection;
+
+public interface SharingManager<S extends Shareable, IM extends InvitationMessage> {
+
+	/** Returns the unique ID of the group sharing client. */
+	ClientId getClientId();
+
+	/**
+	 * Sends an invitation to share the given group with the given contact
+	 * and sends an optional message along with it.
+	 */
+	void sendInvitation(GroupId groupId, ContactId contactId,
+			String message)	throws DbException;
+
+	/**
+	 * Responds to a pending group invitation
+	 */
+	void respondToInvitation(S s, Contact c, boolean accept)
+			throws DbException;
+
+	/**
+	 * Returns all group sharing messages sent by the Contact
+	 * identified by contactId.
+	 */
+	Collection<IM> getInvitationMessages(
+			ContactId contactId) throws DbException;
+
+	/** Returns all groups to which the user could subscribe. */
+	Collection<S> getAvailable() throws DbException;
+
+	/** Returns all contacts who are sharing the given group with us. */
+	Collection<Contact> getSharedBy(GroupId g) throws DbException;
+
+	/** Returns the IDs of all contacts with whom the given group is shared. */
+	Collection<Contact> getSharedWith(GroupId g) throws DbException;
+
+	/** Returns true if the group not already shared and no invitation is open */
+	boolean canBeShared(GroupId g, Contact c) throws DbException;
+
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingMessage.java b/briar-api/src/org/briarproject/api/sharing/SharingMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..78433ff6fa5ba601ff71a0a9c6c83cbb5bbefdae
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/sharing/SharingMessage.java
@@ -0,0 +1,122 @@
+package org.briarproject.api.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfEntry;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.sync.GroupId;
+
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.TYPE;
+
+public interface SharingMessage {
+
+	abstract class BaseMessage {
+		private final GroupId groupId;
+		private final SessionId sessionId;
+
+		public BaseMessage(GroupId groupId, SessionId sessionId) {
+
+			this.groupId = groupId;
+			this.sessionId = sessionId;
+		}
+
+		public BdfList toBdfList() {
+			return BdfList.of(getType(), getSessionId());
+		}
+
+		public abstract BdfDictionary toBdfDictionary();
+
+		protected BdfDictionary toBdfDictionaryHelper() {
+			return BdfDictionary.of(
+					new BdfEntry(TYPE, getType()),
+					new BdfEntry(GROUP_ID, groupId),
+					new BdfEntry(SESSION_ID, sessionId)
+			);
+		}
+
+		public static BaseMessage from(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;
+		}
+	}
+
+	abstract class Invitation extends BaseMessage {
+
+		protected final String message;
+
+		public Invitation(GroupId groupId, SessionId sessionId,
+				String message) {
+
+			super(groupId, sessionId);
+
+			this.message = message;
+		}
+
+		@Override
+		public long getType() {
+			return SHARE_MSG_TYPE_INVITATION;
+		}
+
+		public String getMessage() {
+			return message;
+		}
+	}
+
+	class SimpleMessage extends BaseMessage {
+
+		private final long type;
+
+		public SimpleMessage(long type, GroupId groupId, SessionId sessionId) {
+			super(groupId, sessionId);
+			this.type = type;
+		}
+
+		@Override
+		public long getType() {
+			return type;
+		}
+
+		@Override
+		public BdfDictionary toBdfDictionary() {
+			return toBdfDictionaryHelper();
+		}
+
+		public static SimpleMessage from(long type, GroupId groupId,
+				BdfDictionary d) throws FormatException {
+
+			if (type != SHARE_MSG_TYPE_ACCEPT &&
+					type != SHARE_MSG_TYPE_DECLINE &&
+					type != SHARE_MSG_TYPE_LEAVE &&
+					type != SHARE_MSG_TYPE_ABORT) throw new FormatException();
+
+			SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
+			return new SimpleMessage(type, groupId, sessionId);
+		}
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java
index 6847f461220bebd4b3131b09af88e6d253c5c677..d7b0c1bee1c8c725d7ede60ecd9f11356adef455 100644
--- a/briar-core/src/org/briarproject/CoreEagerSingletons.java
+++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java
@@ -10,6 +10,7 @@ import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.plugins.PluginsModule;
 import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.system.SystemModule;
 import org.briarproject.transport.TransportModule;
@@ -36,6 +37,8 @@ public interface CoreEagerSingletons {
 
 	void inject(PropertiesModule.EagerSingletons init);
 
+	void inject(SharingModule.EagerSingletons init);
+
 	void inject(SyncModule.EagerSingletons init);
 
 	void inject(SystemModule.EagerSingletons init);
diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java
index 6a909b3f6d8110b63a4348cee50012283ef6d2d2..140f0b7e67f1ef6437c65a7ca1d052b0568786c5 100644
--- a/briar-core/src/org/briarproject/CoreModule.java
+++ b/briar-core/src/org/briarproject/CoreModule.java
@@ -20,6 +20,7 @@ import org.briarproject.properties.PropertiesModule;
 import org.briarproject.reliability.ReliabilityModule;
 import org.briarproject.reporting.ReportingModule;
 import org.briarproject.settings.SettingsModule;
+import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.system.SystemModule;
 import org.briarproject.transport.TransportModule;
@@ -47,6 +48,7 @@ import dagger.Module;
 		ReliabilityModule.class,
 		ReportingModule.class,
 		SettingsModule.class,
+		SharingModule.class,
 		SyncModule.class,
 		SystemModule.class,
 		TransportModule.class
@@ -63,6 +65,7 @@ public class CoreModule {
 		c.inject(new MessagingModule.EagerSingletons());
 		c.inject(new PluginsModule.EagerSingletons());
 		c.inject(new PropertiesModule.EagerSingletons());
+		c.inject(new SharingModule.EagerSingletons());
 		c.inject(new SyncModule.EagerSingletons());
 		c.inject(new SystemModule.EagerSingletons());
 		c.inject(new TransportModule.EagerSingletons());
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index 59f56af2acf07df4a8595430239f8cf8f541cc13..8c84509b143bf3c45a082b9829d3acdb9e016fa3 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -1,18 +1,12 @@
 package org.briarproject.forum;
 
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.forum.ForumFactory;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPostFactory;
-import org.briarproject.api.forum.ForumSharingManager;
 import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
@@ -31,10 +25,6 @@ public class ForumModule {
 	public static class EagerSingletons {
 		@Inject
 		ForumPostValidator forumPostValidator;
-		@Inject
-		ForumSharingValidator forumSharingValidator;
-		@Inject
-		ForumSharingManager forumSharingManager;
 	}
 
 	@Provides
@@ -68,37 +58,4 @@ public class ForumModule {
 		return validator;
 	}
 
-	@Provides
-	@Singleton
-	ForumSharingValidator provideSharingValidator(
-			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
-			MetadataEncoder metadataEncoder, Clock clock) {
-
-		ForumSharingValidator validator = new ForumSharingValidator(clientHelper,
-				metadataEncoder, clock);
-		messageQueueManager.registerMessageValidator(
-				ForumSharingManagerImpl.CLIENT_ID, validator);
-
-		return validator;
-	}
-
-	@Provides
-	@Singleton
-	ForumSharingManager provideForumSharingManager(
-			LifecycleManager lifecycleManager,
-			ContactManager contactManager,
-			MessageQueueManager messageQueueManager,
-			ForumManager forumManager,
-			ForumSharingManagerImpl forumSharingManager) {
-
-		lifecycleManager.registerClient(forumSharingManager);
-		contactManager.registerAddContactHook(forumSharingManager);
-		contactManager.registerRemoveContactHook(forumSharingManager);
-		messageQueueManager.registerIncomingMessageHook(
-				ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
-		forumManager.registerRemoveForumHook(forumSharingManager);
-
-		return forumSharingManager;
-	}
-
 }
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java b/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java
deleted file mode 100644
index dc2e69bfb38ecbc5ec72f1d83080c1618207f3ad..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/forum/ForumSharingSessionState.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package org.briarproject.forum;
-
-import org.briarproject.api.FormatException;
-import org.briarproject.api.clients.SessionId;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.sync.GroupId;
-import org.briarproject.api.sync.MessageId;
-
-import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
-import static org.briarproject.api.forum.ForumConstants.FORUM_ID;
-import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
-import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
-import static org.briarproject.api.forum.ForumConstants.STATE;
-import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
-
-// This class is not thread-safe
-public abstract class ForumSharingSessionState {
-
-	private final SessionId sessionId;
-	private final MessageId storageId;
-	private final GroupId groupId;
-	private final ContactId contactId;
-	private final GroupId forumId;
-	private final String forumName;
-	private final byte[] forumSalt;
-	private int task = -1; // TODO get rid of task, see #376
-
-	public ForumSharingSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, ContactId contactId, GroupId forumId,
-			String forumName, byte[] forumSalt) {
-
-		this.sessionId = sessionId;
-		this.storageId = storageId;
-		this.groupId = groupId;
-		this.contactId = contactId;
-		this.forumId = forumId;
-		this.forumName = forumName;
-		this.forumSalt = forumSalt;
-	}
-
-	public static ForumSharingSessionState fromBdfDictionary(BdfDictionary d)
-			throws FormatException{
-
-		SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
-		MessageId messageId = new MessageId(d.getRaw(STORAGE_ID));
-		GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
-		ContactId contactId = new ContactId(d.getLong(CONTACT_ID).intValue());
-		GroupId forumId = new GroupId(d.getRaw(FORUM_ID));
-		String forumName = d.getString(FORUM_NAME);
-		byte[] forumSalt = d.getRaw(FORUM_SALT);
-
-		int intState = d.getLong(STATE).intValue();
-		if (d.getBoolean(IS_SHARER)) {
-			SharerSessionState.State state =
-					SharerSessionState.State.fromValue(intState);
-			return new SharerSessionState(sessionId, messageId, groupId, state,
-					contactId, forumId, forumName, forumSalt);
-		} else {
-			InviteeSessionState.State state =
-					InviteeSessionState.State.fromValue(intState);
-			return new InviteeSessionState(sessionId, messageId, groupId, state,
-					contactId, forumId, forumName, forumSalt);
-		}
-	}
-
-	public BdfDictionary toBdfDictionary() {
-		BdfDictionary d = new BdfDictionary();
-		d.put(SESSION_ID, getSessionId());
-		d.put(STORAGE_ID, getStorageId());
-		d.put(GROUP_ID, getGroupId());
-		d.put(CONTACT_ID, getContactId().getInt());
-		d.put(FORUM_ID, getForumId());
-		d.put(FORUM_NAME, getForumName());
-		d.put(FORUM_SALT, getForumSalt());
-
-		return d;
-	}
-
-	public SessionId getSessionId() {
-		return sessionId;
-	}
-
-	public MessageId getStorageId() {
-		return storageId;
-	}
-
-	public GroupId getGroupId() {
-		return groupId;
-	}
-
-	public ContactId getContactId() {
-		return contactId;
-	}
-
-	public GroupId getForumId() {
-		return forumId;
-	}
-
-	public String getForumName() {
-		return forumName;
-	}
-
-	public byte[] getForumSalt() {
-		return forumSalt;
-	}
-
-	public void setTask(int task) {
-		this.task = task;
-	}
-
-	public int getTask() {
-		return task;
-	}
-
-}
\ No newline at end of file
diff --git a/briar-core/src/org/briarproject/forum/InviteeEngine.java b/briar-core/src/org/briarproject/forum/InviteeEngine.java
deleted file mode 100644
index c7c9ff6223c159fb163d6e6504d81c5600cc4246..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/forum/InviteeEngine.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package org.briarproject.forum;
-
-import org.briarproject.api.FormatException;
-import org.briarproject.api.clients.ProtocolEngine;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.ForumInvitationReceivedEvent;
-import org.briarproject.api.forum.Forum;
-import org.briarproject.api.forum.ForumFactory;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
-import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
-import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
-import static org.briarproject.forum.InviteeSessionState.Action;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ABORT;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
-import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
-import static org.briarproject.forum.InviteeSessionState.State;
-import static org.briarproject.forum.InviteeSessionState.State.ERROR;
-import static org.briarproject.forum.InviteeSessionState.State.FINISHED;
-import static org.briarproject.forum.InviteeSessionState.State.LEFT;
-
-public class InviteeEngine
-		implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
-
-	private final ForumFactory forumFactory;
-	private static final Logger LOG =
-			Logger.getLogger(SharerEngine.class.getName());
-
-	InviteeEngine(ForumFactory forumFactory) {
-		this.forumFactory = forumFactory;
-	}
-
-	@Override
-	public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
-			InviteeSessionState localState, Action action) {
-
-		try {
-			State currentState = localState.getState();
-			State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			if (action == LOCAL_ABORT && currentState != ERROR) {
-				return abortSession(currentState, localState);
-			}
-
-			if (nextState == 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 == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
-				BaseMessage msg;
-				if (action == LOCAL_ACCEPT) {
-					localState.setTask(TASK_ADD_SHARED_FORUM);
-					msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
-							localState.getGroupId(), localState.getSessionId());
-				} else {
-					localState.setTask(
-							TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
-					msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
-							localState.getGroupId(), localState.getSessionId());
-				}
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
-			}
-			else if (action == LOCAL_LEAVE) {
-				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
-						localState.getGroupId(), localState.getSessionId());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, localState, msg);
-			}
-			else {
-				throw new IllegalArgumentException("Unknown Local Action");
-			}
-			return new StateUpdate<InviteeSessionState, BaseMessage>(false,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	@Override
-	public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
-			InviteeSessionState localState, BaseMessage msg) {
-
-		try {
-			State currentState = localState.getState();
-			Action action = Action.getRemote(msg.getType());
-			State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			logMessageReceived(currentState, nextState, msg.getType(), msg);
-
-			if (nextState == ERROR) {
-				if (currentState != 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 == 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 == REMOTE_LEAVE && currentState == FINISHED) {
-				localState.setTask(TASK_UNSHARE_FORUM_SHARED_WITH_US);
-			}
-			else if (currentState == 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 == REMOTE_LEAVE) {
-				localState.setTask(TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
-			}
-			// we have just received our invitation
-			else if (action == REMOTE_INVITATION) {
-				Forum forum = forumFactory
-						.createForum(localState.getForumName(),
-								localState.getForumSalt());
-				localState.setTask(TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
-				ContactId contactId = localState.getContactId();
-				Event event = new ForumInvitationReceivedEvent(forum, contactId);
-				events = Collections.singletonList(event);
-			}
-			else {
-				throw new IllegalArgumentException("Bad state");
-			}
-			return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	private void logLocalAction(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(State currentState, 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<InviteeSessionState, BaseMessage> onMessageDelivered(
-			InviteeSessionState 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<InviteeSessionState, BaseMessage> abortSession(
-			State currentState, InviteeSessionState localState)
-			throws FormatException {
-
-		if (LOG.isLoggable(WARNING)) {
-			LOG.warning("Aborting protocol session " +
-					localState.getSessionId().hashCode() +
-					" in state " + currentState.name());
-		}
-		localState.setState(ERROR);
-		BaseMessage msg =
-				new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
-						localState.getSessionId());
-		List<BaseMessage> messages = Collections.singletonList(msg);
-
-		List<Event> events = Collections.emptyList();
-
-		return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
-				localState, messages, events);
-	}
-
-	private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
-			InviteeSessionState localState, boolean delete) throws FormatException {
-
-		return new StateUpdate<InviteeSessionState, BaseMessage>(delete, false,
-				localState, Collections.<BaseMessage>emptyList(),
-				Collections.<Event>emptyList());
-	}
-}
diff --git a/briar-core/src/org/briarproject/forum/SharerEngine.java b/briar-core/src/org/briarproject/forum/SharerEngine.java
deleted file mode 100644
index 4a142c039523413f5e18d982f51c55e62d3fbf0e..0000000000000000000000000000000000000000
--- a/briar-core/src/org/briarproject/forum/SharerEngine.java
+++ /dev/null
@@ -1,228 +0,0 @@
-package org.briarproject.forum;
-
-import org.briarproject.api.FormatException;
-import org.briarproject.api.clients.ProtocolEngine;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
-import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
-import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
-import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
-import static org.briarproject.forum.SharerSessionState.Action;
-import static org.briarproject.forum.SharerSessionState.Action.LOCAL_ABORT;
-import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
-import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
-import static org.briarproject.forum.SharerSessionState.State;
-import static org.briarproject.forum.SharerSessionState.State.ERROR;
-import static org.briarproject.forum.SharerSessionState.State.FINISHED;
-import static org.briarproject.forum.SharerSessionState.State.LEFT;
-
-public class SharerEngine
-		implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
-
-	private static final Logger LOG =
-			Logger.getLogger(SharerEngine.class.getName());
-
-	@Override
-	public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
-			SharerSessionState localState, Action action) {
-
-		try {
-			State currentState = localState.getState();
-			State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			if (action == LOCAL_ABORT && currentState != ERROR) {
-				return abortSession(currentState, localState);
-			}
-
-			if (nextState == 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 == LOCAL_INVITATION) {
-				BaseMessage msg = new Invitation(localState.getGroupId(),
-						localState.getSessionId(), localState.getForumName(),
-						localState.getForumSalt(), localState.getMessage());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, nextState, msg);
-
-				// remember that we offered to share this forum
-				localState.setTask(TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
-			}
-			else if (action == LOCAL_LEAVE) {
-				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
-						localState.getGroupId(), localState.getSessionId());
-				messages = Collections.singletonList(msg);
-				logLocalAction(currentState, nextState, msg);
-			}
-			else {
-				throw new IllegalArgumentException("Unknown Local Action");
-			}
-			return new StateUpdate<SharerSessionState, BaseMessage>(false,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	@Override
-	public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
-			SharerSessionState localState, BaseMessage msg) {
-
-		try {
-			State currentState = localState.getState();
-			Action action = Action.getRemote(msg.getType());
-			State nextState = currentState.next(action);
-			localState.setState(nextState);
-
-			logMessageReceived(currentState, nextState, msg.getType(), msg);
-
-			if (nextState == ERROR) {
-				if (currentState != 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 == LEFT) {
-				// ignore and delete messages coming in while in that state
-				deleteMsg = true;
-			}
-			else if (action == REMOTE_LEAVE) {
-				localState.setTask(TASK_UNSHARE_FORUM_SHARED_BY_US);
-			}
-			else if (currentState == 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_FORUM);
-				} else {
-					// this ensures that the forum can be shared again
-					localState.setTask(
-							TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
-				}
-				String name = localState.getForumName();
-				ContactId c = localState.getContactId();
-				Event event = new ForumInvitationResponseReceivedEvent(name, c);
-				events = Collections.singletonList(event);
-			}
-			else {
-				throw new IllegalArgumentException("Bad state");
-			}
-			return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
-					false, localState, messages, events);
-		} catch (FormatException e) {
-			throw new IllegalArgumentException(e);
-		}
-	}
-
-	private void logLocalAction(State currentState, 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(State currentState, 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<SharerSessionState, BaseMessage> onMessageDelivered(
-			SharerSessionState 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<SharerSessionState, BaseMessage> abortSession(
-			State currentState, SharerSessionState localState)
-			throws FormatException {
-
-		if (LOG.isLoggable(WARNING)) {
-			LOG.warning("Aborting protocol session " +
-					localState.getSessionId().hashCode() +
-					" in state " + currentState.name());
-		}
-
-		localState.setState(ERROR);
-		BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
-				localState.getGroupId(), localState.getSessionId());
-		List<BaseMessage> messages = Collections.singletonList(msg);
-
-		List<Event> events = Collections.emptyList();
-
-		return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
-				localState, messages, events);
-	}
-
-	private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
-			SharerSessionState localState, boolean delete)
-			throws FormatException {
-
-		return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
-				localState, Collections.<BaseMessage>emptyList(),
-				Collections.<Event>emptyList());
-	}
-
-}
diff --git a/briar-core/src/org/briarproject/sharing/BlogInviteeSessionState.java b/briar-core/src/org/briarproject/sharing/BlogInviteeSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bb3295b7b02a925718b08ca2da3875a25d58980
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/BlogInviteeSessionState.java
@@ -0,0 +1,57 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
+
+public class BlogInviteeSessionState extends InviteeSessionState {
+
+	private final String blogTitle;
+	private final String blogDesc;
+	private final String blogAuthorName;
+	private final byte[] blogPublicKey;
+
+	public BlogInviteeSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId blogId,
+			String blogTitle, String blogDesc, String blogAuthorName,
+			byte[] blogPublicKey) {
+		super(sessionId, storageId, groupId, state, contactId, blogId);
+
+		this.blogTitle = blogTitle;
+		this.blogDesc = blogDesc;
+		this.blogAuthorName = blogAuthorName;
+		this.blogPublicKey = blogPublicKey;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(BLOG_TITLE, getBlogTitle());
+		d.put(BLOG_DESC, getBlogDesc());
+		d.put(BLOG_AUTHOR_NAME, getBlogAuthorName());
+		d.put(BLOG_PUBLIC_KEY, getBlogPublicKey());
+		return d;
+	}
+
+	public String getBlogTitle() {
+		return blogTitle;
+	}
+
+	public String getBlogDesc() {
+		return blogDesc;
+	}
+
+	public String getBlogAuthorName() {
+		return blogAuthorName;
+	}
+
+	public byte[] getBlogPublicKey() {
+		return blogPublicKey;
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharerSessionState.java b/briar-core/src/org/briarproject/sharing/BlogSharerSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..16ef1355b2183ab4d45c7074fef9e4b5c37b5e41
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/BlogSharerSessionState.java
@@ -0,0 +1,57 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
+
+public class BlogSharerSessionState extends SharerSessionState {
+
+	private final String blogTitle;
+	private final String blogDesc;
+	private final String blogAuthorName;
+	private final byte[] blogPublicKey;
+
+	public BlogSharerSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId blogId,
+			String blogTitle, String blogDesc, String blogAuthorName,
+			byte[] blogPublicKey) {
+		super(sessionId, storageId, groupId, state, contactId, blogId);
+
+		this.blogTitle = blogTitle;
+		this.blogDesc = blogDesc;
+		this.blogAuthorName = blogAuthorName;
+		this.blogPublicKey = blogPublicKey;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(BLOG_TITLE, getBlogTitle());
+		d.put(BLOG_DESC, getBlogDesc());
+		d.put(BLOG_AUTHOR_NAME, getBlogAuthorName());
+		d.put(BLOG_PUBLIC_KEY, getBlogPublicKey());
+		return d;
+	}
+
+	public String getBlogTitle() {
+		return blogTitle;
+	}
+
+	public String getBlogDesc() {
+		return blogDesc;
+	}
+
+	public String getBlogAuthorName() {
+		return blogAuthorName;
+	}
+
+	public byte[] getBlogPublicKey() {
+		return blogPublicKey;
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba67ff635ee898c342994773cf8f5b4dd9787160
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
@@ -0,0 +1,293 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogFactory;
+import org.briarproject.api.blogs.BlogInvitationMessage;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogSharingManager;
+import org.briarproject.api.blogs.BlogSharingMessage.BlogInvitation;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.MessageQueueManager;
+import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.event.BlogInvitationReceivedEvent;
+import org.briarproject.api.event.BlogInvitationResponseReceivedEvent;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
+import org.briarproject.util.StringUtils;
+
+import java.security.SecureRandom;
+
+import javax.inject.Inject;
+
+import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
+
+class BlogSharingManagerImpl extends
+		SharingManagerImpl<Blog, BlogInvitation, BlogInvitationMessage, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationReceivedEvent, BlogInvitationResponseReceivedEvent>
+		implements BlogSharingManager {
+
+	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
+			"bee438b5de0b3a685badc4e49d76e72d"
+					+ "21e01c4b569a775112756bdae267a028"));
+
+	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,
+			PrivateGroupFactory privateGroupFactory, SecureRandom random) {
+
+		super(db, messageQueueManager, clientHelper, metadataParser,
+				metadataEncoder, random, privateGroupFactory, clock);
+		this.blogManager = blogManager;
+
+		sFactory = new SFactory(authorFactory, blogFactory, blogManager);
+		iFactory = new IFactory();
+		isFactory = new ISFactory();
+		ssFactory = new SSFactory();
+		irFactory = new IRFactory(sFactory);
+		irrFactory = new IRRFactory();
+	}
+
+	@Override
+	public ClientId getClientId() {
+		return CLIENT_ID;
+	}
+
+	@Override
+	protected ClientId getShareableClientId() {
+		return blogManager.getClientId();
+	}
+
+	@Override
+	protected BlogInvitationMessage createInvitationMessage(MessageId id,
+			BlogInvitation msg, ContactId contactId, boolean available,
+			long time, boolean local, boolean sent, boolean seen,
+			boolean read) {
+		return new BlogInvitationMessage(id, msg.getSessionId(), contactId,
+				msg.getBlogTitle(), msg.getMessage(), available, time, local,
+				sent, seen, read);
+	}
+
+	@Override
+	protected ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> getSFactory() {
+		return sFactory;
+	}
+
+	@Override
+	protected InvitationFactory<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, BlogInvitationReceivedEvent> getIRFactory() {
+		return irFactory;
+	}
+
+	@Override
+	protected InvitationResponseReceivedEventFactory<BlogSharerSessionState, BlogInvitationResponseReceivedEvent> getIRRFactory() {
+		return irrFactory;
+	}
+
+	static class SFactory implements
+			ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> {
+
+		private final AuthorFactory authorFactory;
+		private final BlogFactory blogFactory;
+		private final BlogManager blogManager;
+
+		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(f.getName(), f.getDescription(),
+					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(2).getString(0),
+							shareable.getList(2).getRaw(1));
+			return blogFactory
+					.createBlog(shareable.getString(0), shareable.getString(1),
+							author);
+		}
+
+		@Override
+		public Blog parse(BlogInvitation msg) {
+			Author author = authorFactory.createAuthor(msg.getBlogAuthorName(),
+					msg.getBlogPublicKey());
+			return blogFactory
+					.createBlog(msg.getBlogTitle(), msg.getBlogDesc(), author);
+		}
+
+		public Blog parse(BlogInviteeSessionState state) {
+			Author author = authorFactory
+					.createAuthor(state.getBlogAuthorName(),
+							state.getBlogPublicKey());
+			return blogFactory
+					.createBlog(state.getBlogTitle(), state.getBlogDesc(),
+							author);
+		}
+
+		@Override
+		public Blog parse(BlogSharerSessionState state) {
+			Author author = authorFactory
+					.createAuthor(state.getBlogAuthorName(),
+							state.getBlogPublicKey());
+			return blogFactory
+					.createBlog(state.getBlogTitle(), state.getBlogDesc(),
+							author);
+		}
+	}
+
+	static class IFactory implements
+			InvitationFactory<BlogInvitation, BlogSharerSessionState> {
+		@Override
+		public BlogInvitation build(GroupId groupId, BdfDictionary d)
+				throws FormatException {
+			return BlogInvitation.from(groupId, d);
+		}
+
+		@Override
+		public BlogInvitation build(BlogSharerSessionState localState) {
+			return new BlogInvitation(localState.getGroupId(),
+					localState.getSessionId(), localState.getBlogTitle(),
+					localState.getBlogDesc(), localState.getBlogAuthorName(),
+					localState.getBlogPublicKey(), localState.getMessage());
+		}
+	}
+
+	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 blogTitle = d.getString(BLOG_TITLE);
+			String blogDesc = d.getString(BLOG_DESC);
+			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
+			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
+			return new BlogInviteeSessionState(sessionId, storageId,
+					groupId, state, contactId, blogId, blogTitle, blogDesc,
+					blogAuthorName, blogPublicKey);
+		}
+
+		@Override
+		public BlogInviteeSessionState build(SessionId sessionId,
+				MessageId storageId, GroupId groupId,
+				InviteeSessionState.State state, ContactId contactId,
+				Blog blog) {
+			return new BlogInviteeSessionState(sessionId, storageId,
+					groupId, state, contactId, blog.getId(), blog.getName(),
+					blog.getDescription(), blog.getAuthor().getName(),
+					blog.getAuthor().getPublicKey());
+		}
+	}
+
+	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 blogTitle = d.getString(BLOG_TITLE);
+			String blogDesc = d.getString(BLOG_DESC);
+			String blogAuthorName = d.getString(BLOG_AUTHOR_NAME);
+			byte[] blogPublicKey = d.getRaw(BLOG_PUBLIC_KEY);
+			return new BlogSharerSessionState(sessionId, storageId,
+					groupId, state, contactId, blogId, blogTitle, blogDesc,
+					blogAuthorName, blogPublicKey);
+		}
+
+		@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.getName(),
+					blog.getDescription(), blog.getAuthor().getName(),
+					blog.getAuthor().getPublicKey());
+		}
+	}
+
+	static class IRFactory implements
+			InvitationReceivedEventFactory<BlogInviteeSessionState, BlogInvitationReceivedEvent> {
+
+		private final SFactory sFactory;
+
+		IRFactory(SFactory sFactory) {
+			this.sFactory = sFactory;
+		}
+
+		@Override
+		public BlogInvitationReceivedEvent build(
+				BlogInviteeSessionState localState) {
+			Blog blog = sFactory.parse(localState);
+			ContactId contactId = localState.getContactId();
+			return new BlogInvitationReceivedEvent(blog, contactId);
+		}
+	}
+
+	static class IRRFactory implements
+			InvitationResponseReceivedEventFactory<BlogSharerSessionState, BlogInvitationResponseReceivedEvent> {
+		@Override
+		public BlogInvitationResponseReceivedEvent build(
+				BlogSharerSessionState localState) {
+			String title = localState.getBlogTitle();
+			ContactId c = localState.getContactId();
+			return new BlogInvitationResponseReceivedEvent(title, c);
+		}
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..26b89bb52c512c118e02ea5075226e42b5829eda
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java
@@ -0,0 +1,98 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.BdfMessageContext;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.system.Clock;
+import org.briarproject.clients.BdfMessageValidator;
+
+import javax.inject.Inject;
+
+import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
+import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
+import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.TIME;
+import static org.briarproject.api.sharing.SharingConstants.TYPE;
+import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+
+class BlogSharingValidator extends BdfMessageValidator {
+
+	@Inject
+	BlogSharingValidator(ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		super(clientHelper, metadataEncoder, clock);
+	}
+
+	@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, 5, 6);
+
+			String name = body.getString(2);
+			checkLength(name, 1, MAX_BLOG_TITLE_LENGTH);
+
+			String desc = body.getString(3);
+			checkLength(desc, 1, MAX_BLOG_DESC_LENGTH);
+
+			BdfList author = body.getList(4);
+			checkSize(author, 2);
+
+			String authorName = author.getString(0);
+			checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
+
+			byte[] publicKey = author.getRaw(1);
+			checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
+
+			d.put(BLOG_TITLE, name);
+			d.put(BLOG_DESC, desc);
+			d.put(BLOG_AUTHOR_NAME, authorName);
+			d.put(BLOG_PUBLIC_KEY, publicKey);
+
+			if (body.size() > 5) {
+				String msg = body.getString(5);
+				checkLength(msg, 0, MAX_MESSAGE_BODY_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);
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/ForumInviteeSessionState.java b/briar-core/src/org/briarproject/sharing/ForumInviteeSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8ac1629b121deef1f45dd777af78df2b920e9ea
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/ForumInviteeSessionState.java
@@ -0,0 +1,40 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
+import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
+
+public class ForumInviteeSessionState extends InviteeSessionState {
+
+	private final String forumName;
+	private final byte[] forumSalt;
+
+	public ForumInviteeSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId forumId,
+			String forumName, byte[] forumSalt) {
+		super(sessionId, storageId, groupId, state, contactId, forumId);
+
+		this.forumName = forumName;
+		this.forumSalt = forumSalt;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(FORUM_NAME, getForumName());
+		d.put(FORUM_SALT, getForumSalt());
+		return d;
+	}
+
+	public String getForumName() {
+		return forumName;
+	}
+
+	public byte[] getForumSalt() {
+		return forumSalt;
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/ForumSharerSessionState.java b/briar-core/src/org/briarproject/sharing/ForumSharerSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfc0c231c7f90fe53ac5c90232417867a1288706
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/ForumSharerSessionState.java
@@ -0,0 +1,40 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
+import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
+
+public class ForumSharerSessionState extends SharerSessionState {
+
+	private final String forumName;
+	private final byte[] forumSalt;
+
+	public ForumSharerSessionState(SessionId sessionId, MessageId storageId,
+			GroupId groupId, State state, ContactId contactId, GroupId forumId,
+			String forumName, byte[] forumSalt) {
+		super(sessionId, storageId, groupId, state, contactId, forumId);
+
+		this.forumName = forumName;
+		this.forumSalt = forumSalt;
+	}
+
+	public BdfDictionary toBdfDictionary() {
+		BdfDictionary d = super.toBdfDictionary();
+		d.put(FORUM_NAME, getForumName());
+		d.put(FORUM_SALT, getForumSalt());
+		return d;
+	}
+
+	public String getForumName() {
+		return forumName;
+	}
+
+	public byte[] getForumSalt() {
+		return forumSalt;
+	}
+}
diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb06dff4ca6ade66985921d35bce2169bb64c56c
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
@@ -0,0 +1,269 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.MessageQueueManager;
+import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.data.MetadataParser;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.event.ForumInvitationReceivedEvent;
+import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
+import org.briarproject.api.forum.Forum;
+import org.briarproject.api.forum.ForumFactory;
+import org.briarproject.api.forum.ForumInvitationMessage;
+import org.briarproject.api.forum.ForumManager;
+import org.briarproject.api.forum.ForumSharingManager;
+import org.briarproject.api.forum.ForumSharingMessage.ForumInvitation;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
+import org.briarproject.util.StringUtils;
+
+import java.security.SecureRandom;
+
+import javax.inject.Inject;
+
+import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
+import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
+
+class ForumSharingManagerImpl extends
+		SharingManagerImpl<Forum, ForumInvitation, ForumInvitationMessage, ForumInviteeSessionState, ForumSharerSessionState, ForumInvitationReceivedEvent, ForumInvitationResponseReceivedEvent>
+		implements ForumSharingManager, ForumManager.RemoveForumHook {
+
+	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
+			"cd11a5d04dccd9e2931d6fc3df456313"
+					+ "63bb3e9d9d0e9405fccdb051f41f5449"));
+
+	private final ForumManager forumManager;
+
+	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
+	ForumSharingManagerImpl(ClientHelper clientHelper,
+			Clock clock, DatabaseComponent db,
+			ForumFactory forumFactory,
+			ForumManager forumManager,
+			MessageQueueManager messageQueueManager,
+			MetadataEncoder metadataEncoder,
+			MetadataParser metadataParser,
+			PrivateGroupFactory privateGroupFactory,
+			SecureRandom random) {
+		super(db, messageQueueManager, clientHelper, metadataParser,
+				metadataEncoder, random, privateGroupFactory, clock);
+		this.forumManager = forumManager;
+
+		sFactory = new SFactory(forumFactory, forumManager);
+		iFactory = new IFactory();
+		isFactory = new ISFactory();
+		ssFactory = new SSFactory();
+		irFactory = new IRFactory(sFactory);
+		irrFactory = new IRRFactory();
+	}
+
+	@Override
+	public ClientId getClientId() {
+		return CLIENT_ID;
+	}
+
+	@Override
+	protected ClientId getShareableClientId() {
+		return forumManager.getClientId();
+	}
+
+	@Override
+	protected ForumInvitationMessage createInvitationMessage(MessageId id,
+			ForumInvitation msg, ContactId contactId, boolean available,
+			long time, boolean local, boolean sent, boolean seen,
+			boolean read) {
+		return new ForumInvitationMessage(id, msg.getSessionId(), contactId,
+				msg.getForumName(), msg.getMessage(), available, time, local,
+				sent, seen, read);
+	}
+
+	@Override
+	protected ShareableFactory<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState> getSFactory() {
+		return sFactory;
+	}
+
+	@Override
+	protected InvitationFactory<ForumInvitation, ForumSharerSessionState> getIFactory() {
+		return iFactory;
+	}
+
+	@Override
+	protected InviteeSessionStateFactory<Forum, ForumInviteeSessionState> getISFactory() {
+		return isFactory;
+	}
+
+	@Override
+	protected SharerSessionStateFactory<Forum, ForumSharerSessionState> getSSFactory() {
+		return ssFactory;
+	}
+
+	@Override
+	protected InvitationReceivedEventFactory<ForumInviteeSessionState, ForumInvitationReceivedEvent> getIRFactory() {
+		return irFactory;
+	}
+
+	@Override
+	protected InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> getIRRFactory() {
+		return irrFactory;
+	}
+
+	@Override
+	public void removingForum(Transaction txn, Forum f) throws DbException {
+		removingShareable(txn, f);
+	}
+
+	static class SFactory implements
+			ShareableFactory<Forum, ForumInvitation, ForumInviteeSessionState, ForumSharerSessionState> {
+
+		private final ForumFactory forumFactory;
+		private final ForumManager forumManager;
+
+		SFactory(ForumFactory forumFactory, ForumManager forumManager) {
+			this.forumFactory = forumFactory;
+			this.forumManager = forumManager;
+		}
+
+		@Override
+		public BdfList encode(Forum f) {
+			return BdfList.of(f.getName(), f.getSalt());
+		}
+
+		@Override
+		public Forum get(Transaction txn, GroupId groupId)
+				throws DbException {
+			return forumManager.getForum(txn, groupId);
+		}
+
+		@Override
+		public Forum parse(BdfList shareable) throws FormatException {
+			return forumFactory
+					.createForum(shareable.getString(0), shareable.getRaw(1));
+		}
+
+		@Override
+		public Forum parse(ForumInvitation msg) {
+			return forumFactory
+					.createForum(msg.getForumName(), msg.getForumSalt());
+		}
+
+		public Forum parse(ForumInviteeSessionState state) {
+			return forumFactory
+					.createForum(state.getForumName(), state.getForumSalt());
+		}
+
+		@Override
+		public Forum parse(ForumSharerSessionState state) {
+			return forumFactory
+					.createForum(state.getForumName(), state.getForumSalt());
+		}
+	}
+
+	static class IFactory implements
+			InvitationFactory<ForumInvitation, ForumSharerSessionState> {
+		@Override
+		public ForumInvitation build(GroupId groupId, BdfDictionary d)
+				throws FormatException {
+			return ForumInvitation.from(groupId, d);
+		}
+
+		@Override
+		public ForumInvitation build(ForumSharerSessionState localState) {
+			return new ForumInvitation(localState.getGroupId(),
+					localState.getSessionId(), localState.getForumName(),
+					localState.getForumSalt(), localState.getMessage());
+		}
+	}
+
+	static class ISFactory implements
+			InviteeSessionStateFactory<Forum, ForumInviteeSessionState> {
+		@Override
+		public ForumInviteeSessionState build(SessionId sessionId,
+				MessageId storageId, GroupId groupId,
+				InviteeSessionState.State state, ContactId contactId,
+				GroupId forumId, BdfDictionary d) throws FormatException {
+			String forumName = d.getString(FORUM_NAME);
+			byte[] forumSalt = d.getRaw(FORUM_SALT);
+			return new ForumInviteeSessionState(sessionId, storageId,
+					groupId, state, contactId, forumId, forumName, forumSalt);
+		}
+
+		@Override
+		public ForumInviteeSessionState build(SessionId sessionId,
+				MessageId storageId, GroupId groupId,
+				InviteeSessionState.State state, ContactId contactId,
+				Forum forum) {
+			return new ForumInviteeSessionState(sessionId, storageId,
+					groupId, state, contactId, forum.getId(), forum.getName(),
+					forum.getSalt());
+		}
+	}
+
+	static class SSFactory implements
+			SharerSessionStateFactory<Forum, ForumSharerSessionState> {
+		@Override
+		public ForumSharerSessionState build(SessionId sessionId,
+				MessageId storageId, GroupId groupId,
+				SharerSessionState.State state, ContactId contactId,
+				GroupId forumId, BdfDictionary d) throws FormatException {
+			String forumName = d.getString(FORUM_NAME);
+			byte[] forumSalt = d.getRaw(FORUM_SALT);
+			return new ForumSharerSessionState(sessionId, storageId,
+					groupId, state, contactId, forumId, forumName, forumSalt);
+		}
+
+		@Override
+		public ForumSharerSessionState build(SessionId sessionId,
+				MessageId storageId, GroupId groupId,
+				SharerSessionState.State state, ContactId contactId,
+				Forum forum) {
+			return new ForumSharerSessionState(sessionId, storageId,
+					groupId, state, contactId, forum.getId(), forum.getName(),
+					forum.getSalt());
+		}
+	}
+
+	static class IRFactory implements
+			InvitationReceivedEventFactory<ForumInviteeSessionState, ForumInvitationReceivedEvent> {
+
+		private final SFactory sFactory;
+
+		IRFactory(SFactory sFactory) {
+			this.sFactory = sFactory;
+		}
+
+		@Override
+		public ForumInvitationReceivedEvent build(
+				ForumInviteeSessionState localState) {
+			Forum forum = sFactory.parse(localState);
+			ContactId contactId = localState.getContactId();
+			return new ForumInvitationReceivedEvent(forum, contactId);
+		}
+	}
+
+	static class IRRFactory implements
+			InvitationResponseReceivedEventFactory<ForumSharerSessionState, ForumInvitationResponseReceivedEvent> {
+		@Override
+		public ForumInvitationResponseReceivedEvent build(
+				ForumSharerSessionState localState) {
+			String name = localState.getForumName();
+			ContactId c = localState.getContactId();
+			return new ForumInvitationResponseReceivedEvent(name, c);
+		}
+	}
+}
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingValidator.java b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
similarity index 72%
rename from briar-core/src/org/briarproject/forum/ForumSharingValidator.java
rename to briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
index 5009588869a5b4c49a9242809a5a9a340e28c4a3..702466bea05df11eb5887b354b41891ea25f4d80 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingValidator.java
+++ b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java
@@ -1,4 +1,4 @@
-package org.briarproject.forum;
+package org.briarproject.sharing;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
@@ -12,25 +12,27 @@ import org.briarproject.api.sync.Message;
 import org.briarproject.api.system.Clock;
 import org.briarproject.clients.BdfMessageValidator;
 
+import javax.inject.Inject;
+
 import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
-import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
-import static org.briarproject.api.forum.ForumConstants.LOCAL;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.TIME;
-import static org.briarproject.api.forum.ForumConstants.TYPE;
+import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG;
+import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.TIME;
+import static org.briarproject.api.sharing.SharingConstants.TYPE;
 import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 
 class ForumSharingValidator extends BdfMessageValidator {
 
+	@Inject
 	ForumSharingValidator(ClientHelper clientHelper,
 			MetadataEncoder metadataEncoder, Clock clock) {
 		super(clientHelper, metadataEncoder, clock);
diff --git a/briar-core/src/org/briarproject/sharing/InvitationFactory.java b/briar-core/src/org/briarproject/sharing/InvitationFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..be82501e8f0c8d1e360eea579f3c75f45d1836d5
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/InvitationFactory.java
@@ -0,0 +1,9 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.sharing.SharingMessage;
+
+public interface InvitationFactory<I extends SharingMessage.Invitation, SS extends SharerSessionState> extends
+		org.briarproject.api.sharing.InvitationFactory<I> {
+
+	I build(SS localState);
+}
diff --git a/briar-core/src/org/briarproject/sharing/InvitationReceivedEventFactory.java b/briar-core/src/org/briarproject/sharing/InvitationReceivedEventFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..bdedeee488b931c38605c306290a21d2adce1608
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/InvitationReceivedEventFactory.java
@@ -0,0 +1,8 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.event.InvitationReceivedEvent;
+
+public interface InvitationReceivedEventFactory<IS extends InviteeSessionState, IR extends InvitationReceivedEvent> {
+
+	IR build(IS localState);
+}
diff --git a/briar-core/src/org/briarproject/sharing/InvitationResponseReceivedEventFactory.java b/briar-core/src/org/briarproject/sharing/InvitationResponseReceivedEventFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..4420f30edd003c2727e162649fa218943a99cf91
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/InvitationResponseReceivedEventFactory.java
@@ -0,0 +1,8 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.event.InvitationResponseReceivedEvent;
+
+public interface InvitationResponseReceivedEventFactory<SS extends SharerSessionState, IRR extends InvitationResponseReceivedEvent> {
+
+	IRR build(SS localState);
+}
diff --git a/briar-core/src/org/briarproject/sharing/InviteeEngine.java b/briar-core/src/org/briarproject/sharing/InviteeEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..e786d9a74b59891c20c4a79cb9dce856542698d5
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/InviteeEngine.java
@@ -0,0 +1,222 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ProtocolEngine;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.InvitationReceivedEvent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
+import static org.briarproject.api.sharing.SharingMessage.SimpleMessage;
+
+public class InviteeEngine<IS extends InviteeSessionState, IR extends InvitationReceivedEvent>
+		implements ProtocolEngine<InviteeSessionState.Action, IS, BaseMessage> {
+
+	private static final Logger LOG =
+			Logger.getLogger(InviteeEngine.class.getName());
+
+	private final InvitationReceivedEventFactory<IS, IR> invitationReceivedEventFactory;
+
+	InviteeEngine(InvitationReceivedEventFactory<IS, IR> invitationReceivedEventFactory) {
+		this.invitationReceivedEventFactory = invitationReceivedEventFactory;
+	}
+
+	@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.getGroupId(), localState.getSessionId());
+				} else {
+					localState.setTask(
+							TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US);
+					msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
+							localState.getGroupId(), localState.getSessionId());
+				}
+				messages = Collections.singletonList(msg);
+				logLocalAction(currentState, localState, msg);
+			}
+			else if (action == InviteeSessionState.Action.LOCAL_LEAVE) {
+				BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
+						localState.getGroupId(), localState.getSessionId());
+				messages = Collections.singletonList(msg);
+				logLocalAction(currentState, localState, msg);
+			}
+			else {
+				throw new IllegalArgumentException("Unknown Local Action");
+			}
+			return new StateUpdate<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);
+				Event event = invitationReceivedEventFactory.build(localState);
+				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.getGroupId(),
+						localState.getSessionId());
+		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/org/briarproject/forum/InviteeSessionState.java b/briar-core/src/org/briarproject/sharing/InviteeSessionState.java
similarity index 66%
rename from briar-core/src/org/briarproject/forum/InviteeSessionState.java
rename to briar-core/src/org/briarproject/sharing/InviteeSessionState.java
index 68b397bc726767ecc15b52f72526425470908725..6082c7ba8dfa3100ad56d539b419f14e853cb8e3 100644
--- a/briar-core/src/org/briarproject/forum/InviteeSessionState.java
+++ b/briar-core/src/org/briarproject/sharing/InviteeSessionState.java
@@ -1,4 +1,4 @@
-package org.briarproject.forum;
+package org.briarproject.sharing;
 
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.ContactId;
@@ -6,28 +6,27 @@ import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
-import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.STATE;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
-import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
-import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.STATE;
+import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_ACCEPT;
+import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_DECLINE;
+import static org.briarproject.sharing.InviteeSessionState.Action.LOCAL_LEAVE;
+import static org.briarproject.sharing.InviteeSessionState.Action.REMOTE_INVITATION;
+import static org.briarproject.sharing.InviteeSessionState.Action.REMOTE_LEAVE;
 
 // This class is not thread-safe
-public class InviteeSessionState extends ForumSharingSessionState {
+public abstract class InviteeSessionState extends SharingSessionState {
 
 	private State state;
 
 	public InviteeSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId, GroupId forumId,
-			String forumName, byte[] forumSalt) {
+			GroupId groupId, State state, ContactId contactId,
+			GroupId shareableId) {
 
-		super(sessionId, storageId, groupId, contactId, forumId, forumName,
-				forumSalt);
+		super(sessionId, storageId, groupId, contactId, shareableId);
 		this.state = state;
 	}
 
diff --git a/briar-core/src/org/briarproject/sharing/InviteeSessionStateFactory.java b/briar-core/src/org/briarproject/sharing/InviteeSessionStateFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..57b8adcfaa4359a1e4dbdddf72c3841f467fa2e2
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/InviteeSessionStateFactory.java
@@ -0,0 +1,19 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sharing.Shareable;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+public 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);
+}
diff --git a/briar-core/src/org/briarproject/sharing/ShareableFactory.java b/briar-core/src/org/briarproject/sharing/ShareableFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f961270da321073d3b9746b6ee217b7672aa4da
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/ShareableFactory.java
@@ -0,0 +1,24 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.sharing.Shareable;
+import org.briarproject.api.sharing.SharingMessage;
+import org.briarproject.api.sync.GroupId;
+
+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/org/briarproject/sharing/SharerEngine.java b/briar-core/src/org/briarproject/sharing/SharerEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b09727c50a94bbd98a44d5bde199705c568d061
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/SharerEngine.java
@@ -0,0 +1,225 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ProtocolEngine;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.InvitationResponseReceivedEvent;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
+import static org.briarproject.api.sharing.SharingMessage.Invitation;
+import static org.briarproject.api.sharing.SharingMessage.SimpleMessage;
+
+public 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 InvitationFactory<I, SS> invitationFactory;
+	private final InvitationResponseReceivedEventFactory<SS, IRR>
+			invitationResponseReceivedEventFactory;
+
+	SharerEngine(InvitationFactory<I, SS> invitationFactory,
+			InvitationResponseReceivedEventFactory<SS, IRR> invitationResponseReceivedEventFactory) {
+		this.invitationFactory = invitationFactory;
+		this.invitationResponseReceivedEventFactory =
+				invitationResponseReceivedEventFactory;
+	}
+
+	@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);
+				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.getGroupId(), localState.getSessionId());
+				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 == SharerSessionState.Action.REMOTE_ACCEPT ||
+					action == SharerSessionState.Action.REMOTE_DECLINE) {
+				if (action == SharerSessionState.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);
+				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.getGroupId(), localState.getSessionId());
+		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/org/briarproject/forum/SharerSessionState.java b/briar-core/src/org/briarproject/sharing/SharerSessionState.java
similarity index 66%
rename from briar-core/src/org/briarproject/forum/SharerSessionState.java
rename to briar-core/src/org/briarproject/sharing/SharerSessionState.java
index 6653648530e7e07a830ec50e5381f75e5a9c6e84..6cbb271561aea089a18e2e58357d4dabc7d88472 100644
--- a/briar-core/src/org/briarproject/forum/SharerSessionState.java
+++ b/briar-core/src/org/briarproject/sharing/SharerSessionState.java
@@ -1,4 +1,4 @@
-package org.briarproject.forum;
+package org.briarproject.sharing;
 
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.ContactId;
@@ -6,30 +6,29 @@ import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
-import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.STATE;
-import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
-import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
-import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.STATE;
+import static org.briarproject.sharing.SharerSessionState.Action.LOCAL_INVITATION;
+import static org.briarproject.sharing.SharerSessionState.Action.LOCAL_LEAVE;
+import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_ACCEPT;
+import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_DECLINE;
+import static org.briarproject.sharing.SharerSessionState.Action.REMOTE_LEAVE;
 
 // This class is not thread-safe
-public class SharerSessionState extends ForumSharingSessionState {
+public abstract class SharerSessionState extends SharingSessionState {
 
 	private State state;
 	private String msg = null;
 
 	public SharerSessionState(SessionId sessionId, MessageId storageId,
-			GroupId groupId, State state, ContactId contactId, GroupId forumId,
-			String forumName, byte[] forumSalt) {
+			GroupId groupId, State state, ContactId contactId,
+			GroupId shareableId) {
 
-		super(sessionId, storageId, groupId, contactId, forumId, forumName,
-				forumSalt);
+		super(sessionId, storageId, groupId, contactId, shareableId);
 		this.state = state;
 	}
 
diff --git a/briar-core/src/org/briarproject/sharing/SharerSessionStateFactory.java b/briar-core/src/org/briarproject/sharing/SharerSessionStateFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d045114fc4710062ee142f660efbb4f86d07e8da
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/SharerSessionStateFactory.java
@@ -0,0 +1,19 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sharing.Shareable;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+public 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/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
similarity index 65%
rename from briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
rename to briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
index c03d781da293085113a06620c3385a1ba640fd0c..0d55b4a3cef39ef2fdb61609485968f343975c03 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
@@ -1,4 +1,4 @@
-package org.briarproject.forum;
+package org.briarproject.sharing;
 
 import org.briarproject.api.Bytes;
 import org.briarproject.api.FormatException;
@@ -22,12 +22,12 @@ import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.Event;
-import org.briarproject.api.forum.Forum;
-import org.briarproject.api.forum.ForumFactory;
-import org.briarproject.api.forum.ForumInvitationMessage;
-import org.briarproject.api.forum.ForumManager;
-import org.briarproject.api.forum.ForumSharingManager;
+import org.briarproject.api.event.InvitationReceivedEvent;
+import org.briarproject.api.event.InvitationResponseReceivedEvent;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sharing.InvitationMessage;
+import org.briarproject.api.sharing.Shareable;
+import org.briarproject.api.sharing.SharingManager;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -50,83 +50,90 @@ import java.util.Map;
 import java.util.Set;
 import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.clients.ProtocolEngine.StateUpdate;
-import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
-import static org.briarproject.api.forum.ForumConstants.FORUM_ID;
-import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
-import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
-import static org.briarproject.api.forum.ForumConstants.LOCAL;
-import static org.briarproject.api.forum.ForumConstants.READ;
-import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
-import static org.briarproject.api.forum.ForumConstants.SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
-import static org.briarproject.api.forum.ForumConstants.STATE;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
-import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
-import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
-import static org.briarproject.api.forum.ForumConstants.TIME;
-import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US;
-import static org.briarproject.api.forum.ForumConstants.TYPE;
-import static org.briarproject.api.forum.ForumManager.RemoveForumHook;
-import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
-import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
-import static org.briarproject.forum.ForumSharingSessionState.fromBdfDictionary;
-import static org.briarproject.forum.SharerSessionState.Action;
-
-class ForumSharingManagerImpl extends BdfIncomingMessageHook
-		implements ForumSharingManager, Client, RemoveForumHook,
-		AddContactHook, RemoveContactHook {
-
-	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
-			"cd11a5d04dccd9e2931d6fc3df456313"
-					+ "63bb3e9d9d0e9405fccdb051f41f5449"));
+import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID;
+import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
+import static org.briarproject.api.sharing.SharingConstants.LOCAL;
+import static org.briarproject.api.sharing.SharingConstants.READ;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
+import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
+import static org.briarproject.api.sharing.SharingConstants.SHARING_SALT_LENGTH;
+import static org.briarproject.api.sharing.SharingConstants.STATE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_ADD_SHARED_SHAREABLE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_REMOVE_SHAREABLE_FROM_LIST_TO_BE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_SHARE_SHAREABLE;
+import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TASK_UNSHARE_SHAREABLE_SHARED_WITH_US;
+import static org.briarproject.api.sharing.SharingConstants.TIME;
+import static org.briarproject.api.sharing.SharingConstants.TO_BE_SHARED_BY_US;
+import static org.briarproject.api.sharing.SharingConstants.TYPE;
+import static org.briarproject.api.sharing.SharingMessage.BaseMessage;
+import static org.briarproject.api.sharing.SharingMessage.Invitation;
+
+abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM extends InvitationMessage, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent>
+		extends BdfIncomingMessageHook
+		implements SharingManager<S, IM>, Client, AddContactHook,
+		RemoveContactHook {
 
 	private static final Logger LOG =
-			Logger.getLogger(ForumSharingManagerImpl.class.getName());
+			Logger.getLogger(SharingManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
-	private final ForumManager forumManager;
 	private final MessageQueueManager messageQueueManager;
 	private final MetadataEncoder metadataEncoder;
 	private final SecureRandom random;
 	private final PrivateGroupFactory privateGroupFactory;
-	private final ForumFactory forumFactory;
 	private final Clock clock;
 	private final Group localGroup;
 
-	@Inject
-	ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager,
+	SharingManagerImpl(DatabaseComponent db,
 			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
 			MetadataParser metadataParser, MetadataEncoder metadataEncoder,
 			SecureRandom random, PrivateGroupFactory privateGroupFactory,
-			ForumFactory forumFactory, Clock clock) {
+			Clock clock) {
 
 		super(clientHelper, metadataParser);
 		this.db = db;
-		this.forumManager = forumManager;
 		this.messageQueueManager = messageQueueManager;
 		this.metadataEncoder = metadataEncoder;
 		this.random = random;
 		this.privateGroupFactory = privateGroupFactory;
-		this.forumFactory = forumFactory;
 		this.clock = clock;
 		localGroup = privateGroupFactory.createLocalGroup(getClientId());
 	}
 
+	public abstract ClientId getClientId();
+
+	protected abstract ClientId getShareableClientId();
+
+	protected abstract IM createInvitationMessage(MessageId id, I msg,
+			ContactId contactId, boolean available, long time, boolean local,
+			boolean sent, boolean seen, boolean read);
+
+	protected abstract ShareableFactory<S, I, IS, SS> getSFactory();
+
+	protected abstract InvitationFactory<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);
@@ -183,7 +190,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	protected void incomingMessage(Transaction txn, Message m, BdfList body,
 			BdfDictionary d) throws DbException, FormatException {
 
-		BaseMessage msg = BaseMessage.from(m.getGroupId(), d);
+		BaseMessage msg = BaseMessage.from(getIFactory(), m.getGroupId(), d);
 		SessionId sessionId = msg.getSessionId();
 
 		if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
@@ -200,19 +207,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				// check if we already have a state with that sessionId
 				if (stateExists) throw new FormatException();
 
-				// check if forum can be shared
-				Invitation invitation = (Invitation) msg;
-				Forum f = forumFactory.createForum(invitation.getForumName(),
-						invitation.getForumSalt());
+				// 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
-				InviteeSessionState state =
-						initializeInviteeState(txn, contactId, invitation);
-				InviteeEngine engine = new InviteeEngine(forumFactory);
+				IS state = initializeInviteeState(txn, contactId, invitation);
+				InviteeEngine<IS, IR> engine =
+						new InviteeEngine<IS, IR>(getIRFactory());
 				processInviteeStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			} catch (FormatException e) {
@@ -222,48 +228,47 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		} else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
 				msg.getType() == SHARE_MSG_TYPE_DECLINE) {
 			// we are a sharer who just received a response
-			SharerSessionState state = getSessionStateForSharer(txn, sessionId);
-			SharerEngine engine = new SharerEngine();
+			SS state = getSessionStateForSharer(txn, sessionId);
+			SharerEngine<I, SS, IRR> engine =
+					new SharerEngine<I, SS, IRR>(getIFactory(),
+							getIRRFactory());
 			processSharerStateUpdate(txn, m.getId(),
 					engine.onMessageReceived(state, msg));
 		} else if (msg.getType() == SHARE_MSG_TYPE_LEAVE ||
 				msg.getType() == SHARE_MSG_TYPE_ABORT) {
 			// we don't know who we are, so figure it out
-			ForumSharingSessionState s = getSessionState(txn, sessionId, true);
+			SharingSessionState s = getSessionState(txn, sessionId, true);
 			if (s instanceof SharerSessionState) {
 				// we are a sharer and the invitee wants to leave or abort
-				SharerSessionState state = (SharerSessionState) s;
-				SharerEngine engine = new SharerEngine();
+				SS state = (SS) s;
+				SharerEngine<I, SS, IRR> engine =
+						new SharerEngine<I, SS, IRR>(getIFactory(),
+								getIRRFactory());
 				processSharerStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			} else {
 				// we are an invitee and the sharer wants to leave or abort
-				InviteeSessionState state = (InviteeSessionState) s;
-				InviteeEngine engine = new InviteeEngine(forumFactory);
+				IS state = (IS) s;
+				InviteeEngine<IS, IR> engine =
+						new InviteeEngine<IS, IR>(getIRFactory());
 				processInviteeStateUpdate(txn, m.getId(),
 						engine.onMessageReceived(state, msg));
 			}
 		} else {
 			// message has passed validator, so that should never happen
-			throw new RuntimeException("Illegal Forum Sharing Message");
+			throw new RuntimeException("Illegal Sharing Message");
 		}
 	}
 
 	@Override
-	public ClientId getClientId() {
-		return CLIENT_ID;
-	}
-
-	@Override
-	public void sendForumInvitation(GroupId groupId, ContactId contactId,
+	public void sendInvitation(GroupId groupId, ContactId contactId,
 			String msg) throws DbException {
 
 		Transaction txn = db.startTransaction(false);
 		try {
 			// initialize local state for sharer
-			Forum f = forumManager.getForum(txn, groupId);
-			SharerSessionState localState =
-					initializeSharerState(txn, f, contactId);
+			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)) {
@@ -271,9 +276,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 			}
 
 			// start engine and process its state update
-			SharerEngine engine = new SharerEngine();
+			SharerEngine<I, SS, IRR> engine =
+					new SharerEngine<I, SS, IRR>(getIFactory(),
+							getIRRFactory());
 			processSharerStateUpdate(txn, null,
-					engine.onLocalAction(localState, Action.LOCAL_INVITATION));
+					engine.onLocalAction(localState,
+							SharerSessionState.Action.LOCAL_INVITATION));
 
 			txn.setComplete();
 		} catch (FormatException e) {
@@ -284,14 +292,13 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	@Override
-	public void respondToInvitation(Forum f, Contact c, boolean accept)
+	public void respondToInvitation(S f, Contact c, boolean accept)
 			throws DbException {
 
 		Transaction txn = db.startTransaction(false);
 		try {
-			// find session state based on forum
-			InviteeSessionState localState =
-					getSessionStateForResponse(txn, f, c);
+			// find session state based on shareable
+			IS localState = getSessionStateForResponse(txn, f, c);
 
 			// define action
 			InviteeSessionState.Action localAction;
@@ -302,7 +309,8 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 			}
 
 			// start engine and process its state update
-			InviteeEngine engine = new InviteeEngine(forumFactory);
+			InviteeEngine<IS, IR> engine =
+					new InviteeEngine<IS, IR>(getIRFactory());
 			processInviteeStateUpdate(txn, null,
 					engine.onLocalAction(localState, localAction));
 
@@ -315,8 +323,8 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	@Override
-	public Collection<ForumInvitationMessage> getForumInvitationMessages(
-			ContactId contactId) throws DbException {
+	public Collection<IM> getInvitationMessages(ContactId contactId)
+			throws DbException {
 
 		// query for all invitations
 		BdfDictionary query = BdfDictionary.of(
@@ -328,14 +336,13 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 			Contact contact = db.getContact(txn, contactId);
 			Group group = getContactGroup(contact);
 
-			Collection<ForumInvitationMessage> list =
-					new ArrayList<ForumInvitationMessage>();
+			Collection<IM> list = new ArrayList<IM>();
 			Map<MessageId, BdfDictionary> map = clientHelper
 					.getMessageMetadataAsDictionary(txn, group.getId(), query);
 			for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
 				BdfDictionary d = m.getValue();
 				try {
-					Invitation msg = Invitation.from(group.getId(), d);
+					I msg = getIFactory().build(group.getId(), d);
 					MessageStatus status =
 							db.getMessageStatus(txn, contactId, m.getKey());
 					long time = d.getLong(TIME);
@@ -343,20 +350,17 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 					boolean read = d.getBoolean(READ, false);
 					boolean available = false;
 					if (!local) {
-						// figure out whether the forum is still available
-						ForumSharingSessionState s =
+						// figure out whether the shareable is still available
+						SharingSessionState s =
 								getSessionState(txn, msg.getSessionId(), true);
 						if (!(s instanceof InviteeSessionState))
 							continue;
 						available = ((InviteeSessionState) s).getState() ==
 								InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
 					}
-					ForumInvitationMessage im =
-							new ForumInvitationMessage(m.getKey(),
-									msg.getSessionId(), contactId,
-									msg.getForumName(), msg.getMessage(),
-									available, time, local, status.isSent(),
-									status.isSeen(), read);
+					IM im = createInvitationMessage(m.getKey(), msg, contactId,
+							available, time, local, status.isSent(),
+							status.isSeen(), read);
 					list.add(im);
 				} catch (FormatException e) {
 					if (LOG.isLoggable(WARNING))
@@ -373,40 +377,20 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	@Override
-	public void removingForum(Transaction txn, Forum f) throws DbException {
+	public Collection<S> getAvailable() throws DbException {
 		try {
-			for (Contact c : db.getContacts(txn)) {
-				GroupId g = getContactGroup(c).getId();
-				if (removeFromList(txn, g, TO_BE_SHARED_BY_US, f)) {
-					leaveForum(txn, c.getId(), f);
-				}
-				if (removeFromList(txn, g, SHARED_BY_US, f)) {
-					leaveForum(txn, c.getId(), f);
-				}
-				if (removeFromList(txn, g, SHARED_WITH_US, f)) {
-					leaveForum(txn, c.getId(), f);
-				}
-			}
-		} catch (IOException e) {
-			throw new DbException(e);
-		}
-	}
-
-	@Override
-	public Collection<Forum> getAvailableForums() throws DbException {
-		try {
-			Set<Forum> available = new HashSet<Forum>();
+			Set<S> available = new HashSet<S>();
 			Transaction txn = db.startTransaction(true);
 			try {
-				// Get any forums we subscribe to
+				// Get any shareables we subscribe to
 				Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
-						forumManager.getClientId()));
-				// Get all forums shared by contacts
+						getShareableClientId()));
+				// Get all shareables shared by contacts
 				for (Contact c : db.getContacts(txn)) {
 					Group g = getContactGroup(c);
-					List<Forum> forums =
-							getForumList(txn, g.getId(), SHARED_WITH_US);
-					for (Forum f : forums) {
+					List<S> shareables =
+							getShareableList(txn, g.getId(), SHARED_WITH_US);
+					for (S f : shareables) {
 						if (!subscribed.contains(f.getGroup()))
 							available.add(f);
 					}
@@ -489,7 +473,26 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		}
 	}
 
-	private void checkForRaceCondition(Transaction txn, Forum f, Contact c)
+	protected 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();
@@ -507,12 +510,14 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 		if (alice) {
 			// our own invitation takes precedence, so just delete Bob's
-			LOG.info("Invitation race-condition: We are Alice deleting Bob's invitation.");
+			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.");
-			ForumSharingSessionState state =
+			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();
@@ -529,7 +534,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 	}
 
-	private SharerSessionState initializeSharerState(Transaction txn, Forum f,
+	private SS initializeSharerState(Transaction txn, S f,
 			ContactId contactId) throws FormatException, DbException {
 
 		Contact c = db.getContact(txn, contactId);
@@ -537,15 +542,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 		// create local message to keep engine state
 		long now = clock.currentTimeMillis();
-		Bytes salt = new Bytes(new byte[FORUM_SALT_LENGTH]);
+		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());
 
-		SharerSessionState s = new SharerSessionState(sessionId, sessionId,
+		SS s = getSSFactory().build(sessionId, sessionId,
 				group.getId(), SharerSessionState.State.PREPARE_INVITATION,
-				contactId, f.getId(), f.getName(), f.getSalt());
+				contactId, f);
 
 		// save local state to database
 		BdfDictionary d = s.toBdfDictionary();
@@ -554,27 +559,24 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		return s;
 	}
 
-	private InviteeSessionState initializeInviteeState(Transaction txn,
-			ContactId contactId, Invitation msg)
+	private IS initializeInviteeState(Transaction txn,
+			ContactId contactId, I msg)
 			throws FormatException, DbException {
 
 		Contact c = db.getContact(txn, contactId);
 		Group group = getContactGroup(c);
-		String name = msg.getForumName();
-		byte[] salt = msg.getForumSalt();
-		Forum f = forumFactory.createForum(name, salt);
+		S f = getSFactory().parse(msg);
 
 		// create local message to keep engine state
 		long now = clock.currentTimeMillis();
-		Bytes mSalt = new Bytes(new byte[FORUM_SALT_LENGTH]);
+		Bytes mSalt = new Bytes(new byte[SHARING_SALT_LENGTH]);
 		random.nextBytes(mSalt.getBytes());
 		Message m = clientHelper.createMessage(localGroup.getId(), now,
 				BdfList.of(mSalt));
 
-		InviteeSessionState s = new InviteeSessionState(msg.getSessionId(),
+		IS s = getISFactory().build(msg.getSessionId(),
 				m.getId(), group.getId(),
-				InviteeSessionState.State.AWAIT_INVITATION, contactId,
-				f.getId(), f.getName(), f.getSalt());
+				InviteeSessionState.State.AWAIT_INVITATION, contactId, f);
 
 		// save local state to database
 		BdfDictionary d = s.toBdfDictionary();
@@ -583,7 +585,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		return s;
 	}
 
-	private ForumSharingSessionState getSessionState(Transaction txn,
+	private SharingSessionState getSessionState(Transaction txn,
 			SessionId sessionId, boolean warn)
 			throws DbException, FormatException {
 
@@ -612,11 +614,13 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 				}
 				throw new FormatException();
 			}
-			return fromBdfDictionary(map.values().iterator().next());
+			return SharingSessionState
+					.fromBdfDictionary(getISFactory(), getSSFactory(),
+							map.values().iterator().next());
 		}
 	}
 
-	private SharerSessionState getSessionStateForSharer(Transaction txn,
+	private SS getSessionStateForSharer(Transaction txn,
 			SessionId sessionId)
 			throws DbException, FormatException {
 
@@ -626,17 +630,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 		if (!d.getBoolean(IS_SHARER)) throw new FormatException();
 
-		return (SharerSessionState) fromBdfDictionary(d);
+		return (SS) SharingSessionState
+				.fromBdfDictionary(getISFactory(), getSSFactory(), d);
 	}
 
-	private InviteeSessionState getSessionStateForResponse(Transaction txn,
-			Forum f, Contact c) throws DbException, FormatException {
+	private IS getSessionStateForResponse(Transaction txn,
+			S f, Contact c) throws DbException, FormatException {
 
-		// query for invitee states for that forum in state await response
+		// 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(FORUM_ID, f.getId()),
+				new BdfEntry(SHAREABLE_ID, f.getId()),
 				new BdfEntry(STATE,
 						InviteeSessionState.State.AWAIT_LOCAL_RESPONSE
 								.getValue())
@@ -647,7 +652,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 
 		if (map.size() > 1 && LOG.isLoggable(WARNING)) {
 			LOG.warning(
-					"More than one session state found for forum with ID " +
+					"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());
@@ -655,35 +660,38 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		if (map.isEmpty()) {
 			if (LOG.isLoggable(WARNING)) {
 				LOG.warning(
-						"No session state found for forum with ID " +
+						"No session state found for shareable with ID " +
 								Arrays.hashCode(f.getId().getBytes()) +
 								" in state AWAIT_LOCAL_RESPONSE");
 			}
 			throw new DbException();
 		}
-		return (InviteeSessionState) fromBdfDictionary(
-				map.values().iterator().next());
+		return (IS) SharingSessionState
+				.fromBdfDictionary(getISFactory(), getSSFactory(),
+						map.values().iterator().next());
 	}
 
-	private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,
-			Forum f, ContactId c) throws DbException, FormatException {
+	private SharingSessionState getSessionStateForLeaving(Transaction txn,
+			S f, ContactId c) throws DbException, FormatException {
 
 		BdfDictionary query = BdfDictionary.of(
 				new BdfEntry(CONTACT_ID, c.getInt()),
-				new BdfEntry(FORUM_ID, f.getId())
+				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 {
-				ForumSharingSessionState s = fromBdfDictionary(d);
+				SharingSessionState s = SharingSessionState
+						.fromBdfDictionary(getISFactory(), getSSFactory(), d);
 
-				// check that a forum get be left in current session
+				// check that a shareable get be left in current session
 				if (s instanceof SharerSessionState) {
 					SharerSessionState state = (SharerSessionState) s;
 					SharerSessionState.State nextState =
-							state.getState().next(Action.LOCAL_LEAVE);
+							state.getState()
+									.next(SharerSessionState.Action.LOCAL_LEAVE);
 					if (nextState != SharerSessionState.State.ERROR) {
 						return state;
 					}
@@ -703,11 +711,11 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<ForumSharingSessionState, BaseMessage> result)
+			StateUpdate<SharingSessionState, BaseMessage> result, S f)
 			throws DbException, FormatException {
 
 		// perform actions based on new local state
-		performTasks(txn, result.localState);
+		performTasks(txn, result.localState, f);
 
 		// save new local state
 		MessageId storageId = result.localState.getStorageId();
@@ -735,31 +743,37 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private void processSharerStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<SharerSessionState, BaseMessage> result)
+			StateUpdate<SS, BaseMessage> result)
 			throws DbException, FormatException {
 
-		StateUpdate<ForumSharingSessionState, BaseMessage> r =
-				new StateUpdate<ForumSharingSessionState, BaseMessage>(
+		StateUpdate<SharingSessionState, BaseMessage> r =
+				new StateUpdate<SharingSessionState, BaseMessage>(
 						result.deleteMessage, result.deleteState,
 						result.localState, result.toSend, result.toBroadcast);
 
-		processStateUpdate(txn, messageId, r);
+		// get shareable for later
+		S f = getSFactory().parse(result.localState);
+
+		processStateUpdate(txn, messageId, r, f);
 	}
 
 	private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
-			StateUpdate<InviteeSessionState, BaseMessage> result)
+			StateUpdate<IS, BaseMessage> result)
 			throws DbException, FormatException {
 
-		StateUpdate<ForumSharingSessionState, BaseMessage> r =
-				new StateUpdate<ForumSharingSessionState, BaseMessage>(
+		StateUpdate<SharingSessionState, BaseMessage> r =
+				new StateUpdate<SharingSessionState, BaseMessage>(
 						result.deleteMessage, result.deleteState,
 						result.localState, result.toSend, result.toBroadcast);
 
-		processStateUpdate(txn, messageId, r);
+		// get shareable for later
+		S f = getSFactory().parse(result.localState);
+
+		processStateUpdate(txn, messageId, r, f);
 	}
 
-	private void performTasks(Transaction txn, ForumSharingSessionState localState)
-			throws FormatException, DbException {
+	private void performTasks(Transaction txn, SharingSessionState localState,
+			S f) throws FormatException, DbException {
 
 		if (localState.getTask() == -1) return;
 
@@ -772,37 +786,26 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		// get contact ID for later
 		ContactId contactId = localState.getContactId();
 
-		// get forum for later
-		String name = localState.getForumName();
-		byte[] salt = localState.getForumSalt();
-		Forum f = forumFactory.createForum(name, salt);
-
 		// perform tasks
-		if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) {
+		if (task == TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US) {
 			addToList(txn, groupId, SHARED_WITH_US, f);
-		}
-		else if (task == TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US) {
+		} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US) {
 			removeFromList(txn, groupId, SHARED_WITH_US, f);
-		}
-		else if (task == TASK_ADD_SHARED_FORUM) {
+		} else if (task == TASK_ADD_SHARED_SHAREABLE) {
 			db.addGroup(txn, f.getGroup());
 			db.setVisibleToContact(txn, contactId, f.getId(), true);
-		}
-		else if (task == TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US) {
+		} 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_FORUM_FROM_LIST_TO_BE_SHARED_BY_US) {
+		} 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_FORUM) {
+		} else if (task == TASK_SHARE_SHAREABLE) {
 			db.setVisibleToContact(txn, contactId, f.getId(), true);
 			removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
 			addToList(txn, groupId, SHARED_BY_US, f);
-		}
-		else if (task == TASK_UNSHARE_FORUM_SHARED_BY_US) {
+		} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_BY_US) {
 			db.setVisibleToContact(txn, contactId, f.getId(), false);
 			removeFromList(txn, groupId, SHARED_BY_US, f);
-		} else if (task == TASK_UNSHARE_FORUM_SHARED_WITH_US) {
+		} else if (task == TASK_UNSHARE_SHAREABLE_SHARED_WITH_US) {
 			db.setVisibleToContact(txn, contactId, f.getId(), false);
 			removeFromList(txn, groupId, SHARED_WITH_US, f);
 		}
@@ -826,7 +829,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private Group getContactGroup(Contact c) {
-		return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
+		return privateGroupFactory.createPrivateGroup(getClientId(), c);
 	}
 
 	private ContactId getContactId(Transaction txn, GroupId contactGroupId)
@@ -836,90 +839,94 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
 		return new ContactId(meta.getLong(CONTACT_ID).intValue());
 	}
 
-	private void leaveForum(Transaction txn, ContactId c, Forum f)
+	private void leaveShareable(Transaction txn, ContactId c, S f)
 			throws DbException, FormatException {
 
-		ForumSharingSessionState state = getSessionStateForLeaving(txn, f, c);
+		SharingSessionState state = getSessionStateForLeaving(txn, f, c);
 		if (state instanceof SharerSessionState) {
-			Action action = Action.LOCAL_LEAVE;
-			SharerEngine engine = new SharerEngine();
+			SharerSessionState.Action action =
+					SharerSessionState.Action.LOCAL_LEAVE;
+			SharerEngine<I, SS, IRR> engine =
+					new SharerEngine<I, SS, IRR>(getIFactory(),
+							getIRRFactory());
 			processSharerStateUpdate(txn, null,
-					engine.onLocalAction((SharerSessionState) state, action));
+					engine.onLocalAction((SS) state, action));
 		} else {
 			InviteeSessionState.Action action =
 					InviteeSessionState.Action.LOCAL_LEAVE;
-			InviteeEngine engine = new InviteeEngine(forumFactory);
+			InviteeEngine<IS, IR> engine =
+					new InviteeEngine<IS, IR>(getIRFactory());
 			processInviteeStateUpdate(txn, null,
-					engine.onLocalAction((InviteeSessionState) state, action));
+					engine.onLocalAction((IS) state, action));
 		}
 	}
 
 	private boolean listContains(Transaction txn, GroupId contactGroup,
-			GroupId forum, String key) throws DbException, FormatException {
+			GroupId shareable, String key) throws DbException, FormatException {
 
-		List<Forum> list = getForumList(txn, contactGroup, key);
-		for (Forum f : list) {
-			if (f.getId().equals(forum)) return true;
+		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,
-			Forum f) throws DbException, FormatException {
+			S f) throws DbException, FormatException {
 
-		List<Forum> forums = getForumList(txn, groupId, key);
-		if (forums.contains(f)) return false;
-		forums.add(f);
-		storeForumList(txn, groupId, key, forums);
+		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,
-			Forum f) throws DbException, FormatException {
+			S f) throws DbException, FormatException {
 
-		List<Forum> forums = getForumList(txn, groupId, key);
-		if (forums.remove(f)) {
-			storeForumList(txn, groupId, key, forums);
+		List<S> shareables = getShareableList(txn, groupId, key);
+		if (shareables.remove(f)) {
+			storeShareableList(txn, groupId, key, shareables);
 			return true;
 		}
 		return false;
 	}
 
-	private List<Forum> getForumList(Transaction txn, GroupId groupId,
+	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 parseForumList(list);
+		return parseShareableList(list);
 	}
 
-	private void storeForumList(Transaction txn, GroupId groupId, String key,
-			List<Forum> forums)	throws DbException, FormatException {
+	private void storeShareableList(Transaction txn, GroupId groupId,
+			String key,
+			List<S> shareables) throws DbException, FormatException {
 
-		BdfList list = encodeForumList(forums);
+		BdfList list = encodeShareableList(shareables);
 		BdfDictionary metadata = BdfDictionary.of(
 				new BdfEntry(key, list)
 		);
 		clientHelper.mergeGroupMetadata(txn, groupId, metadata);
 	}
 
-	private BdfList encodeForumList(List<Forum> forums) {
-		BdfList forumList = new BdfList();
-		for (Forum f : forums)
-			forumList.add(BdfList.of(f.getName(), f.getSalt()));
-		return forumList;
+	private BdfList encodeShareableList(List<S> shareables) {
+		BdfList shareableList = new BdfList();
+		for (S f : shareables)
+			shareableList.add(getSFactory().encode(f));
+		return shareableList;
 	}
 
-	private List<Forum> parseForumList(BdfList list) throws FormatException {
-		List<Forum> forums = new ArrayList<Forum>(list.size());
+	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 forum = list.getList(i);
-			forums.add(forumFactory
-					.createForum(forum.getString(0), forum.getRaw(1)));
+			BdfList shareable = list.getList(i);
+			shareables.add(getSFactory().parse(shareable));
 		}
-		return forums;
+		return shareables;
 	}
 
 	private void deleteMessage(Transaction txn, MessageId messageId)
diff --git a/briar-core/src/org/briarproject/sharing/SharingModule.java b/briar-core/src/org/briarproject/sharing/SharingModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..d62c7dccf7945810b7d271e85c4ac43e78131268
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/SharingModule.java
@@ -0,0 +1,97 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.blogs.BlogSharingManager;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.MessageQueueManager;
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.forum.ForumManager;
+import org.briarproject.api.forum.ForumSharingManager;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.system.Clock;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class SharingModule {
+
+	public static class EagerSingletons {
+		@Inject
+		BlogSharingValidator blogSharingValidator;
+		@Inject
+		ForumSharingValidator forumSharingValidator;
+		@Inject
+		ForumSharingManager forumSharingManager;
+	}
+
+	@Provides
+	@Singleton
+	BlogSharingValidator provideBlogSharingValidator(
+			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+
+		BlogSharingValidator
+				validator = new BlogSharingValidator(clientHelper,
+				metadataEncoder, clock);
+		messageQueueManager.registerMessageValidator(
+				BlogSharingManagerImpl.CLIENT_ID, validator);
+
+		return validator;
+	}
+
+	@Provides
+	@Singleton
+	BlogSharingManager provideBlogSharingManager(
+			LifecycleManager lifecycleManager,
+			ContactManager contactManager,
+			MessageQueueManager messageQueueManager,
+			BlogSharingManagerImpl blogSharingManager) {
+
+		lifecycleManager.registerClient(blogSharingManager);
+		contactManager.registerAddContactHook(blogSharingManager);
+		contactManager.registerRemoveContactHook(blogSharingManager);
+		messageQueueManager.registerIncomingMessageHook(
+				BlogSharingManagerImpl.CLIENT_ID, blogSharingManager);
+
+		return blogSharingManager;
+	}
+
+	@Provides
+	@Singleton
+	ForumSharingValidator provideForumSharingValidator(
+			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+
+		ForumSharingValidator
+				validator = new ForumSharingValidator(clientHelper,
+				metadataEncoder, clock);
+		messageQueueManager.registerMessageValidator(
+				ForumSharingManagerImpl.CLIENT_ID, validator);
+
+		return validator;
+	}
+
+	@Provides
+	@Singleton
+	ForumSharingManager provideForumSharingManager(
+			LifecycleManager lifecycleManager,
+			ContactManager contactManager,
+			MessageQueueManager messageQueueManager,
+			ForumManager forumManager,
+			ForumSharingManagerImpl forumSharingManager) {
+
+		lifecycleManager.registerClient(forumSharingManager);
+		contactManager.registerAddContactHook(forumSharingManager);
+		contactManager.registerRemoveContactHook(forumSharingManager);
+		messageQueueManager.registerIncomingMessageHook(
+				ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
+		forumManager.registerRemoveForumHook(forumSharingManager);
+
+		return forumSharingManager;
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/sharing/SharingSessionState.java b/briar-core/src/org/briarproject/sharing/SharingSessionState.java
new file mode 100644
index 0000000000000000000000000000000000000000..6864df09ed5d7f70dc93f064aaf2a72316aaa497
--- /dev/null
+++ b/briar-core/src/org/briarproject/sharing/SharingSessionState.java
@@ -0,0 +1,102 @@
+package org.briarproject.sharing;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.SessionId;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID;
+import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
+import static org.briarproject.api.sharing.SharingConstants.IS_SHARER;
+import static org.briarproject.api.sharing.SharingConstants.SESSION_ID;
+import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID;
+import static org.briarproject.api.sharing.SharingConstants.STATE;
+import static org.briarproject.api.sharing.SharingConstants.STORAGE_ID;
+
+// This class is not thread-safe
+public 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
+
+	public 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;
+	}
+
+	public 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, getGroupId());
+		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 getGroupId() {
+		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-tests/src/org/briarproject/forum/ForumSharingManagerImplTest.java b/briar-tests/src/org/briarproject/forum/SharingManagerImplTest.java
similarity index 76%
rename from briar-tests/src/org/briarproject/forum/ForumSharingManagerImplTest.java
rename to briar-tests/src/org/briarproject/forum/SharingManagerImplTest.java
index 07fc1ad80a77dacffcdf1093c7f09a954c1fffe1..bc014f56c1764e64e321e3aaa7a75bf70ed399cf 100644
--- a/briar-tests/src/org/briarproject/forum/ForumSharingManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/forum/SharingManagerImplTest.java
@@ -5,7 +5,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.fail;
 
-public class ForumSharingManagerImplTest extends BriarTestCase {
+public class SharingManagerImplTest extends BriarTestCase {
 
 	@Test
 	public void testUnitTestsExist() {