From 6ece398a213fde230cdee2e12558cf88f99e9082 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Wed, 28 Sep 2016 16:54:28 -0300
Subject: [PATCH] Create PrivateGroupManager Facade and stub implementation

---
 briar-android-tests/build.gradle              |   4 -
 .../BlogSharingIntegrationTestComponent.java  |  15 ---
 .../ForumSharingIntegrationTest.java          |  10 +-
 .../ForumSharingIntegrationTestComponent.java |   4 +-
 .../src/org/briarproject/api/blogs/Blog.java  |  12 +-
 .../briarproject/api/clients/BaseGroup.java   |  46 +++++++
 .../briarproject/api/clients/BaseMessage.java |  28 ++++
 .../BaseMessageHeader.java}                   |   6 +-
 ...pFactory.java => ContactGroupFactory.java} |   6 +-
 .../src/org/briarproject/api/forum/Forum.java |  36 +----
 .../org/briarproject/api/forum/ForumPost.java |  23 ++--
 .../api/introduction/IntroductionMessage.java |   4 +-
 .../api/messaging/PrivateMessage.java         |  23 ++--
 .../api/messaging/PrivateMessageHeader.java   |   3 +-
 .../api/privategroup/GroupMessage.java        |  17 +++
 .../api/privategroup/GroupMessageFactory.java |  20 +++
 .../api/privategroup/GroupMessageHeader.java  |  14 ++
 .../api/privategroup/PrivateGroup.java        |  27 ++++
 .../privategroup/PrivateGroupConstants.java   |  22 +++
 .../api/privategroup/PrivateGroupFactory.java |  20 +++
 .../api/privategroup/PrivateGroupManager.java |  46 +++++++
 .../api/sharing/InvitationMessage.java        |   4 +-
 .../briarproject/clients/ClientsModule.java   |   6 +-
 ...Impl.java => ContactGroupFactoryImpl.java} |   8 +-
 .../briarproject/feed/FeedManagerImpl.java    |  11 +-
 .../IntroductionGroupFactory.java             |  14 +-
 .../messaging/MessagingManagerImpl.java       |  10 +-
 .../briarproject/privategroup/Constants.java  |   8 ++
 .../privategroup/GroupMessageFactoryImpl.java |  43 ++++++
 .../privategroup/GroupMessageValidator.java   |  43 ++++++
 .../privategroup/PrivateGroupFactoryImpl.java |  69 ++++++++++
 .../privategroup/PrivateGroupManagerImpl.java | 125 ++++++++++++++++++
 .../privategroup/PrivateGroupModule.java      |  68 ++++++++++
 .../TransportPropertyManagerImpl.java         |  12 +-
 .../sharing/BlogSharingManagerImpl.java       |   6 +-
 .../sharing/ForumSharingManagerImpl.java      |   6 +-
 .../sharing/SharingManagerImpl.java           |  12 +-
 .../IntroductionManagerImplTest.java          |   6 +-
 38 files changed, 691 insertions(+), 146 deletions(-)
 create mode 100644 briar-api/src/org/briarproject/api/clients/BaseGroup.java
 create mode 100644 briar-api/src/org/briarproject/api/clients/BaseMessage.java
 rename briar-api/src/org/briarproject/api/{messaging/BaseMessage.java => clients/BaseMessageHeader.java} (80%)
 rename briar-api/src/org/briarproject/api/clients/{PrivateGroupFactory.java => ContactGroupFactory.java} (70%)
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/PrivateGroupConstants.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/PrivateGroupFactory.java
 create mode 100644 briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
 rename briar-core/src/org/briarproject/clients/{PrivateGroupFactoryImpl.java => ContactGroupFactoryImpl.java} (85%)
 create mode 100644 briar-core/src/org/briarproject/privategroup/Constants.java
 create mode 100644 briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
 create mode 100644 briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
 create mode 100644 briar-core/src/org/briarproject/privategroup/PrivateGroupFactoryImpl.java
 create mode 100644 briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
 create mode 100644 briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java

diff --git a/briar-android-tests/build.gradle b/briar-android-tests/build.gradle
index d0594e9bc1..914bb06ff9 100644
--- a/briar-android-tests/build.gradle
+++ b/briar-android-tests/build.gradle
@@ -11,10 +11,6 @@ android {
         consumerProguardFiles getDefaultProguardFile('proguard-android.txt'), '../briar-android/proguard-rules.txt'
     }
 
-    dexOptions {
-        incremental true
-    }
-
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java
index 1f738830e5..e0a7ada60e 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java
@@ -2,13 +2,8 @@ package org.briarproject;
 
 import org.briarproject.api.blogs.BlogManager;
 import org.briarproject.api.blogs.BlogSharingManager;
-import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.SyncSessionFactory;
@@ -87,14 +82,4 @@ interface BlogSharingIntegrationTestComponent {
 
 	SyncSessionFactory getSyncSessionFactory();
 
-	/* the following methods are only needed to manually construct messages */
-
-	DatabaseComponent getDatabaseComponent();
-
-	PrivateGroupFactory getPrivateGroupFactory();
-
-	ClientHelper getClientHelper();
-
-	MessageQueueManager getMessageQueueManager();
-
 }
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 d908b647ba..4c4ba9b965 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
@@ -4,7 +4,7 @@ import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.api.Bytes;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -497,9 +497,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			DatabaseComponent db = t0.getDatabaseComponent();
 			MessageQueueManager queue = t0.getMessageQueueManager();
 			Contact c1 = contactManager0.getContact(contactId1);
-			PrivateGroupFactory groupFactory = t0.getPrivateGroupFactory();
+			ContactGroupFactory groupFactory = t0.getContactGroupFactory();
 			Group group = groupFactory
-					.createPrivateGroup(forumSharingManager0.getClientId(), c1);
+					.createContactGroup(forumSharingManager0.getClientId(), c1);
 			long time = clock.currentTimeMillis();
 			BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
 					sessionId.getBytes(),
@@ -693,9 +693,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			DatabaseComponent db = t0.getDatabaseComponent();
 			MessageQueueManager queue = t0.getMessageQueueManager();
 			Contact c1 = contactManager0.getContact(contactId1);
-			PrivateGroupFactory groupFactory = t0.getPrivateGroupFactory();
+			ContactGroupFactory groupFactory = t0.getContactGroupFactory();
 			Group group = groupFactory
-					.createPrivateGroup(forumSharingManager0.getClientId(), c1);
+					.createContactGroup(forumSharingManager0.getClientId(), c1);
 			long time = clock.currentTimeMillis();
 
 			// construct a new message re-using the old SessionId
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 6f3da3c1c0..10dacd7c80 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
@@ -2,7 +2,7 @@ package org.briarproject;
 
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
@@ -90,7 +90,7 @@ interface ForumSharingIntegrationTestComponent {
 
 	DatabaseComponent getDatabaseComponent();
 
-	PrivateGroupFactory getPrivateGroupFactory();
+	ContactGroupFactory getContactGroupFactory();
 
 	ClientHelper getClientHelper();
 
diff --git a/briar-api/src/org/briarproject/api/blogs/Blog.java b/briar-api/src/org/briarproject/api/blogs/Blog.java
index c97e42be2a..5de781e7de 100644
--- a/briar-api/src/org/briarproject/api/blogs/Blog.java
+++ b/briar-api/src/org/briarproject/api/blogs/Blog.java
@@ -1,15 +1,14 @@
 package org.briarproject.api.blogs;
 
-import org.briarproject.api.forum.Forum;
+import org.briarproject.api.clients.BaseGroup;
 import org.briarproject.api.identity.Author;
+import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sync.Group;
 import org.jetbrains.annotations.NotNull;
 
-public class Blog extends Forum {
+public class Blog extends BaseGroup implements Shareable {
 
-	@NotNull
 	private final String description;
-	@NotNull
 	private final Author author;
 
 	public Blog(@NotNull Group group, @NotNull String name,
@@ -29,4 +28,9 @@ public class Blog extends Forum {
 	public Author getAuthor() {
 		return author;
 	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof Blog && super.equals(o);
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/clients/BaseGroup.java b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
new file mode 100644
index 0000000000..7a6d1754e8
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/clients/BaseGroup.java
@@ -0,0 +1,46 @@
+package org.briarproject.api.clients;
+
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class BaseGroup {
+
+	private final Group group;
+	private final String name;
+	private final byte[] salt;
+
+	public BaseGroup(@NotNull Group group, @NotNull String name, byte[] salt) {
+		this.group = group;
+		this.name = name;
+		this.salt = salt;
+	}
+
+	public GroupId getId() {
+		return group.getId();
+	}
+
+	public Group getGroup() {
+		return group;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public byte[] getSalt() {
+		return salt;
+	}
+
+	@Override
+	public int hashCode() {
+		return group.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof BaseGroup &&
+				getGroup().equals(((BaseGroup) o).getGroup());
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/clients/BaseMessage.java b/briar-api/src/org/briarproject/api/clients/BaseMessage.java
new file mode 100644
index 0000000000..776bfcf5c9
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/clients/BaseMessage.java
@@ -0,0 +1,28 @@
+package org.briarproject.api.clients;
+
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class BaseMessage {
+
+	private final Message message;
+	private final MessageId parent;
+
+	public BaseMessage(@NotNull Message message, @Nullable MessageId parent) {
+		this.message = message;
+		this.parent = parent;
+	}
+
+	@NotNull
+	public Message getMessage() {
+		return message;
+	}
+
+	@Nullable
+	public MessageId getParent() {
+		return parent;
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/messaging/BaseMessage.java b/briar-api/src/org/briarproject/api/clients/BaseMessageHeader.java
similarity index 80%
rename from briar-api/src/org/briarproject/api/messaging/BaseMessage.java
rename to briar-api/src/org/briarproject/api/clients/BaseMessageHeader.java
index c83350a09e..852cf0b5dc 100644
--- a/briar-api/src/org/briarproject/api/messaging/BaseMessage.java
+++ b/briar-api/src/org/briarproject/api/clients/BaseMessageHeader.java
@@ -1,14 +1,14 @@
-package org.briarproject.api.messaging;
+package org.briarproject.api.clients;
 
 import org.briarproject.api.sync.MessageId;
 
-public abstract class BaseMessage {
+public abstract class BaseMessageHeader {
 
 	private final MessageId id;
 	private final long timestamp;
 	private final boolean local, read, sent, seen;
 
-	public BaseMessage(MessageId id, long timestamp, boolean local,
+	public BaseMessageHeader(MessageId id, long timestamp, boolean local,
 			boolean read, boolean sent, boolean seen) {
 
 		this.id = id;
diff --git a/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java b/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java
similarity index 70%
rename from briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java
rename to briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java
index 01e2680172..7043bb30d1 100644
--- a/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java
+++ b/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java
@@ -3,12 +3,14 @@ package org.briarproject.api.clients;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupFactory;
 
-public interface PrivateGroupFactory {
+public interface ContactGroupFactory {
 
 	/** Creates a group that is not shared with any contacts. */
 	Group createLocalGroup(ClientId clientId);
 
 	/** Creates a group for the given client to share with the given contact. */
-	Group createPrivateGroup(ClientId clientId, Contact contact);
+	Group createContactGroup(ClientId clientId, Contact contact);
+
 }
diff --git a/briar-api/src/org/briarproject/api/forum/Forum.java b/briar-api/src/org/briarproject/api/forum/Forum.java
index 6162825082..cdaa091b98 100644
--- a/briar-api/src/org/briarproject/api/forum/Forum.java
+++ b/briar-api/src/org/briarproject/api/forum/Forum.java
@@ -1,44 +1,18 @@
 package org.briarproject.api.forum;
 
+import org.briarproject.api.clients.BaseGroup;
 import org.briarproject.api.sharing.Shareable;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.GroupId;
 
-public class Forum implements Shareable {
-
-	private final Group group;
-	private final String name;
-	private final byte[] salt;
+public class Forum extends BaseGroup implements Shareable {
 
 	public Forum(Group group, String name, byte[] salt) {
-		this.group = group;
-		this.name = name;
-		this.salt = salt;
-	}
-
-	public GroupId getId() {
-		return group.getId();
-	}
-
-	public Group getGroup() {
-		return group;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public byte[] getSalt() {
-		return salt;
-	}
-
-	@Override
-	public int hashCode() {
-		return group.hashCode();
+		super(group, name, salt);
 	}
 
 	@Override
 	public boolean equals(Object o) {
-		return o instanceof Forum && group.equals(((Forum) o).group);
+		return o instanceof Forum && super.equals(o);
 	}
+
 }
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPost.java b/briar-api/src/org/briarproject/api/forum/ForumPost.java
index 6e433137b4..aef48c78c4 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPost.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPost.java
@@ -1,30 +1,25 @@
 package org.briarproject.api.forum;
 
+import org.briarproject.api.clients.BaseMessage;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-public class ForumPost {
+public class ForumPost extends BaseMessage {
 
-	private final Message message;
-	private final MessageId parent;
 	private final Author author;
 
-	public ForumPost(Message message, MessageId parent, Author author) {
-		this.message = message;
-		this.parent = parent;
+	public ForumPost(@NotNull Message message, @Nullable MessageId parent,
+			@Nullable Author author) {
+		super(message, parent);
 		this.author = author;
 	}
 
-	public Message getMessage() {
-		return message;
-	}
-
-	public MessageId getParent() {
-		return parent;
-	}
-
+	@Nullable
 	public Author getAuthor() {
 		return author;
 	}
+
 }
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java
index d860726a75..7ddf97aead 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java
@@ -1,13 +1,13 @@
 package org.briarproject.api.introduction;
 
 import org.briarproject.api.clients.SessionId;
-import org.briarproject.api.messaging.BaseMessage;
+import org.briarproject.api.clients.BaseMessageHeader;
 import org.briarproject.api.sync.MessageId;
 
 import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
 import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
 
- public class IntroductionMessage extends BaseMessage {
+ public class IntroductionMessage extends BaseMessageHeader {
 
 	private final SessionId sessionId;
 	private final MessageId messageId;
diff --git a/briar-api/src/org/briarproject/api/messaging/PrivateMessage.java b/briar-api/src/org/briarproject/api/messaging/PrivateMessage.java
index 7a51b374d8..b9354566a3 100644
--- a/briar-api/src/org/briarproject/api/messaging/PrivateMessage.java
+++ b/briar-api/src/org/briarproject/api/messaging/PrivateMessage.java
@@ -1,30 +1,23 @@
 package org.briarproject.api.messaging;
 
+import org.briarproject.api.clients.BaseMessage;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-public class PrivateMessage {
+public class PrivateMessage extends BaseMessage {
 
-	private final Message message;
-	private final MessageId parent;
 	private final String contentType;
 
-	public PrivateMessage(Message message, MessageId parent,
-			String contentType) {
-		this.message = message;
-		this.parent = parent;
+	public PrivateMessage(@NotNull Message message, @Nullable MessageId parent,
+			@NotNull String contentType) {
+		super(message, parent);
 		this.contentType = contentType;
 	}
 
-	public Message getMessage() {
-		return message;
-	}
-
-	public MessageId getParent() {
-		return parent;
-	}
-
 	public String getContentType() {
 		return contentType;
 	}
+
 }
diff --git a/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java b/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java
index f1c8eb51fe..e30a0ba636 100644
--- a/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java
+++ b/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java
@@ -1,8 +1,9 @@
 package org.briarproject.api.messaging;
 
+import org.briarproject.api.clients.BaseMessageHeader;
 import org.briarproject.api.sync.MessageId;
 
-public class PrivateMessageHeader extends BaseMessage {
+public class PrivateMessageHeader extends BaseMessageHeader {
 
 	private final String contentType;
 
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
new file mode 100644
index 0000000000..8ffe6c6a46
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
@@ -0,0 +1,17 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.forum.ForumPost;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class GroupMessage extends ForumPost {
+
+	public GroupMessage(@NotNull Message message, @Nullable MessageId parent,
+			@NotNull Author author) {
+		super(message, parent, author);
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
new file mode 100644
index 0000000000..76f46314e9
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java
@@ -0,0 +1,20 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.crypto.PrivateKey;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+
+import java.security.GeneralSecurityException;
+
+public interface GroupMessageFactory {
+
+	@NotNull
+	GroupMessage createGroupMessage(GroupId groupId, long timestamp,
+			MessageId parent, LocalAuthor author, String body)
+			throws FormatException, GeneralSecurityException;
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
new file mode 100644
index 0000000000..608e86ce0a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java
@@ -0,0 +1,14 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.clients.PostHeader;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.MessageId;
+
+public class GroupMessageHeader extends PostHeader {
+
+	public GroupMessageHeader(MessageId id, MessageId parentId, long timestamp,
+			Author author, Author.Status authorStatus, boolean read) {
+		super(id, parentId, timestamp, author, authorStatus, read);
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
new file mode 100644
index 0000000000..f6d5862c78
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
@@ -0,0 +1,27 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.clients.BaseGroup;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Group;
+import org.jetbrains.annotations.NotNull;
+
+public class PrivateGroup extends BaseGroup {
+
+	private final Author author;
+
+	public PrivateGroup(@NotNull Group group, @NotNull String name,
+			@NotNull Author author, @NotNull byte[] salt) {
+		super(group, name, salt);
+		this.author = author;
+	}
+
+	public Author getAuthor() {
+		return author;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof PrivateGroup && super.equals(o);
+	}
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupConstants.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupConstants.java
new file mode 100644
index 0000000000..989844d0a7
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupConstants.java
@@ -0,0 +1,22 @@
+package org.briarproject.api.privategroup;
+
+import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+
+public interface PrivateGroupConstants {
+
+	/**
+	 * The maximum length of a group's name in UTF-8 bytes.
+	 */
+	int MAX_GROUP_NAME_LENGTH = 100;
+
+	/**
+	 * The length of a group's random salt in bytes.
+	 */
+	int GROUP_SALT_LENGTH = 32;
+
+	/**
+	 * The maximum length of a group post's body in bytes.
+	 */
+	int MAX_GROUP_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupFactory.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupFactory.java
new file mode 100644
index 0000000000..492eabc866
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupFactory.java
@@ -0,0 +1,20 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.identity.Author;
+import org.jetbrains.annotations.NotNull;
+
+public interface PrivateGroupFactory {
+
+	/**
+	 * Creates a private group with the given name and author.
+	 */
+	@NotNull
+	PrivateGroup createPrivateGroup(String name, Author author);
+
+	/**
+	 * Creates a private group with the given name, author and salt.
+	 */
+	@NotNull
+	PrivateGroup createPrivateGroup(String name, Author author, byte[] salt);
+
+}
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
new file mode 100644
index 0000000000..3f2a637624
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
@@ -0,0 +1,46 @@
+package org.briarproject.api.privategroup;
+
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+public interface PrivateGroupManager {
+
+	/** Returns the unique ID of the private group client. */
+	@NotNull
+	ClientId getClientId();
+
+	/** Stores (and sends) a local group message. */
+	void addLocalMessage(GroupMessage p) throws DbException;
+
+	/** Returns the private group with the given ID. */
+	@NotNull
+	PrivateGroup getPrivateGroup(GroupId g) throws DbException;
+
+	/**
+	 * Returns the private group with the given ID within the given transaction.
+	 */
+	@NotNull
+	PrivateGroup getPrivateGroup(Transaction txn, GroupId g) throws DbException;
+
+	/** Returns all private groups the user is a member of. */
+	@NotNull
+	Collection<PrivateGroup> getPrivateGroups() throws DbException;
+
+	/** Returns the body of the group message with the given ID. */
+	@NotNull
+	String getMessageBody(MessageId m) throws DbException;
+
+	/** Returns the headers of all group messages in the given group. */
+	@NotNull
+	Collection<GroupMessageHeader> getHeaders(GroupId g) throws DbException;
+
+	/** Marks a group message as read or unread. */
+	void setReadFlag(MessageId m, boolean read) throws DbException;
+
+}
diff --git a/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java b/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java
index 181a04059e..adc8d8932b 100644
--- a/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java
+++ b/briar-api/src/org/briarproject/api/sharing/InvitationMessage.java
@@ -2,10 +2,10 @@ 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.clients.BaseMessageHeader;
 import org.briarproject.api.sync.MessageId;
 
-public abstract class InvitationMessage extends BaseMessage {
+public abstract class InvitationMessage extends BaseMessageHeader {
 
 	private final SessionId sessionId;
 	private final ContactId contactId;
diff --git a/briar-core/src/org/briarproject/clients/ClientsModule.java b/briar-core/src/org/briarproject/clients/ClientsModule.java
index 1262632c07..9f4160ce3e 100644
--- a/briar-core/src/org/briarproject/clients/ClientsModule.java
+++ b/briar-core/src/org/briarproject/clients/ClientsModule.java
@@ -2,7 +2,7 @@ package org.briarproject.clients;
 
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.QueueMessageFactory;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.BdfReaderFactory;
@@ -33,9 +33,9 @@ public class ClientsModule {
 	}
 
 	@Provides
-	PrivateGroupFactory providePrivateGroupFactory(GroupFactory groupFactory,
+	ContactGroupFactory providePrivateGroupFactory(GroupFactory groupFactory,
 			ClientHelper clientHelper) {
-		return new PrivateGroupFactoryImpl(groupFactory, clientHelper);
+		return new ContactGroupFactoryImpl(groupFactory, clientHelper);
 	}
 
 	@Provides
diff --git a/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java b/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java
similarity index 85%
rename from briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java
rename to briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java
index 56ed9f5300..d3a28dd994 100644
--- a/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java
+++ b/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java
@@ -4,7 +4,7 @@ package org.briarproject.clients;
 import org.briarproject.api.Bytes;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.identity.AuthorId;
@@ -14,7 +14,7 @@ import org.briarproject.api.sync.GroupFactory;
 
 import javax.inject.Inject;
 
-class PrivateGroupFactoryImpl implements PrivateGroupFactory {
+class ContactGroupFactoryImpl implements ContactGroupFactory {
 
 	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
 
@@ -22,7 +22,7 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 	private final ClientHelper clientHelper;
 
 	@Inject
-	PrivateGroupFactoryImpl(GroupFactory groupFactory,
+	ContactGroupFactoryImpl(GroupFactory groupFactory,
 			ClientHelper clientHelper) {
 		this.groupFactory = groupFactory;
 		this.clientHelper = clientHelper;
@@ -34,7 +34,7 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 	}
 
 	@Override
-	public Group createPrivateGroup(ClientId clientId, Contact contact) {
+	public Group createContactGroup(ClientId clientId, Contact contact) {
 		AuthorId local = contact.getLocalAuthorId();
 		AuthorId remote = contact.getAuthor().getId();
 		byte[] descriptor = createGroupDescriptor(local, remote);
diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
index f2ffa7ea9d..fda189d3a7 100644
--- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
+++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
@@ -15,7 +15,7 @@ import org.briarproject.api.blogs.BlogPost;
 import org.briarproject.api.blogs.BlogPostFactory;
 import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
@@ -42,7 +42,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -86,7 +85,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	private final ScheduledExecutorService feedExecutor;
 	private final Executor ioExecutor;
 	private final DatabaseComponent db;
-	private final PrivateGroupFactory privateGroupFactory;
+	private final ContactGroupFactory contactGroupFactory;
 	private final ClientHelper clientHelper;
 	private final IdentityManager identityManager;
 	private final BlogManager blogManager;
@@ -103,14 +102,14 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	@Inject
 	FeedManagerImpl(ScheduledExecutorService feedExecutor,
 			@IoExecutor Executor ioExecutor, DatabaseComponent db,
-			PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper,
+			ContactGroupFactory contactGroupFactory, ClientHelper clientHelper,
 			IdentityManager identityManager, BlogManager blogManager,
 			SocketFactory torSocketFactory) {
 
 		this.feedExecutor = feedExecutor;
 		this.ioExecutor = ioExecutor;
 		this.db = db;
-		this.privateGroupFactory = privateGroupFactory;
+		this.contactGroupFactory = contactGroupFactory;
 		this.clientHelper = clientHelper;
 		this.identityManager = identityManager;
 		this.blogManager = blogManager;
@@ -518,7 +517,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	}
 
 	private Group getLocalGroup() {
-		return privateGroupFactory.createLocalGroup(getClientId());
+		return contactGroupFactory.createLocalGroup(getClientId());
 	}
 
 }
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java b/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java
index f04437d184..8cdd9d0f58 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionGroupFactory.java
@@ -1,6 +1,6 @@
 package org.briarproject.introduction;
 
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.sync.Group;
 
@@ -8,19 +8,19 @@ import javax.inject.Inject;
 
 public class IntroductionGroupFactory {
 
-	final private PrivateGroupFactory privateGroupFactory;
+	final private ContactGroupFactory contactGroupFactory;
 	final private Group localGroup;
 
 	@Inject
-	IntroductionGroupFactory(PrivateGroupFactory privateGroupFactory) {
-		this.privateGroupFactory = privateGroupFactory;
-		localGroup = privateGroupFactory
+	IntroductionGroupFactory(ContactGroupFactory contactGroupFactory) {
+		this.contactGroupFactory = contactGroupFactory;
+		localGroup = contactGroupFactory
 				.createLocalGroup(IntroductionManagerImpl.CLIENT_ID);
 	}
 
 	public Group createIntroductionGroup(Contact c) {
-		return privateGroupFactory
-				.createPrivateGroup(IntroductionManagerImpl.CLIENT_ID, c);
+		return contactGroupFactory
+				.createContactGroup(IntroductionManagerImpl.CLIENT_ID, c);
 	}
 
 	public Group createLocalGroup() {
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index 959c64ad43..d2af3d8a1d 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -3,7 +3,7 @@ package org.briarproject.messaging;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager.AddContactHook;
@@ -41,16 +41,16 @@ class MessagingManagerImpl extends BdfIncomingMessageHook
 					+ "8bf9a6d6021d40d219c86b731b903070"));
 
 	private final DatabaseComponent db;
-	private final PrivateGroupFactory privateGroupFactory;
+	private final ContactGroupFactory contactGroupFactory;
 
 	@Inject
 	MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
 			MetadataParser metadataParser,
-			PrivateGroupFactory privateGroupFactory) {
+			ContactGroupFactory contactGroupFactory) {
 		super(clientHelper, metadataParser);
 
 		this.db = db;
-		this.privateGroupFactory = privateGroupFactory;
+		this.contactGroupFactory = contactGroupFactory;
 	}
 
 	@Override
@@ -79,7 +79,7 @@ class MessagingManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private Group getContactGroup(Contact c) {
-		return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
+		return contactGroupFactory.createContactGroup(CLIENT_ID, c);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java
new file mode 100644
index 0000000000..7a1a4495bf
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/Constants.java
@@ -0,0 +1,8 @@
+package org.briarproject.privategroup;
+
+interface Constants {
+
+	// Database keys
+	String KEY_READ = "read";
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
new file mode 100644
index 0000000000..271f7712c0
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java
@@ -0,0 +1,43 @@
+package org.briarproject.privategroup;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.GroupMessageFactory;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+
+import java.security.GeneralSecurityException;
+
+import javax.inject.Inject;
+
+class GroupMessageFactoryImpl implements GroupMessageFactory {
+
+	private final ClientHelper clientHelper;
+
+	@Inject
+	GroupMessageFactoryImpl(ClientHelper clientHelper) {
+		this.clientHelper = clientHelper;
+	}
+
+	@NotNull
+	@Override
+	public GroupMessage createGroupMessage(GroupId groupId, long timestamp,
+			MessageId parent, LocalAuthor author, String body)
+			throws FormatException, GeneralSecurityException {
+
+		// Generate the signature
+		byte[] sig = clientHelper.sign(new BdfList(), author.getPrivateKey());
+
+		// Compose the message
+		Message m =
+				clientHelper.createMessage(groupId, timestamp, new BdfList());
+
+		return new GroupMessage(m, parent, author);
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
new file mode 100644
index 0000000000..75a8913c07
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
@@ -0,0 +1,43 @@
+package org.briarproject.privategroup;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.BdfMessageContext;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.InvalidMessageException;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
+import org.briarproject.clients.BdfMessageValidator;
+
+import java.util.Collection;
+import java.util.Collections;
+
+class GroupMessageValidator extends BdfMessageValidator {
+
+	private final CryptoComponent crypto;
+	private final AuthorFactory authorFactory;
+
+	GroupMessageValidator(CryptoComponent crypto, AuthorFactory authorFactory,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
+			Clock clock) {
+		super(clientHelper, metadataEncoder, clock);
+		this.crypto = crypto;
+		this.authorFactory = authorFactory;
+	}
+
+	@Override
+	protected BdfMessageContext validateMessage(Message m, Group g,
+			BdfList body) throws InvalidMessageException, FormatException {
+
+		BdfDictionary meta = new BdfDictionary();
+		Collection<MessageId> dependencies = Collections.emptyList();
+		return new BdfMessageContext(meta, dependencies);
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupFactoryImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupFactoryImpl.java
new file mode 100644
index 0000000000..429fd56b5f
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupFactoryImpl.java
@@ -0,0 +1,69 @@
+package org.briarproject.privategroup;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupFactory;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupFactory;
+import org.briarproject.util.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.security.SecureRandom;
+
+import javax.inject.Inject;
+
+import static org.briarproject.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
+import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
+
+class PrivateGroupFactoryImpl implements PrivateGroupFactory {
+
+	private final GroupFactory groupFactory;
+	private final ClientHelper clientHelper;
+	private final SecureRandom random;
+
+	@Inject
+	PrivateGroupFactoryImpl(GroupFactory groupFactory,
+			ClientHelper clientHelper, SecureRandom random) {
+
+		this.groupFactory = groupFactory;
+		this.clientHelper = clientHelper;
+		this.random = random;
+	}
+
+	@NotNull
+	@Override
+	public PrivateGroup createPrivateGroup(String name, Author author) {
+		int length = StringUtils.toUtf8(name).length;
+		if (length == 0) throw new IllegalArgumentException("Group name empty");
+		if (length > MAX_GROUP_NAME_LENGTH)
+			throw new IllegalArgumentException(
+					"Group name exceeds maximum length");
+		byte[] salt = new byte[GROUP_SALT_LENGTH];
+		random.nextBytes(salt);
+		return createPrivateGroup(name, author, salt);
+	}
+
+	@NotNull
+	@Override
+	public PrivateGroup createPrivateGroup(String name, Author author,
+			byte[] salt) {
+		try {
+			BdfList group = BdfList.of(
+					name,
+					author.getName(),
+					author.getPublicKey(),
+					salt
+			);
+			byte[] descriptor = clientHelper.toByteArray(group);
+			Group g = groupFactory
+					.createGroup(PrivateGroupManagerImpl.CLIENT_ID, descriptor);
+			return new PrivateGroup(g, name, author, salt);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
new file mode 100644
index 0000000000..15256467ac
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -0,0 +1,125 @@
+package org.briarproject.privategroup;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+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.identity.Author;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupFactory;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.clients.BdfIncomingMessageHook;
+import org.briarproject.util.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.inject.Inject;
+
+import static org.briarproject.privategroup.Constants.KEY_READ;
+
+public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
+		PrivateGroupManager {
+
+	static final ClientId CLIENT_ID = new ClientId(
+			StringUtils.fromHexString("5072697661746547726f75704d616e61"
+					+ "67657220627920546f727374656e2047"));
+
+	private final DatabaseComponent db;
+	private final IdentityManager identityManager;
+	private final PrivateGroupFactory privateGroupFactory;
+
+	@Inject
+	PrivateGroupManagerImpl(ClientHelper clientHelper,
+			MetadataParser metadataParser, DatabaseComponent db,
+			IdentityManager identityManager,
+			PrivateGroupFactory privateGroupFactory) {
+		super(clientHelper, metadataParser);
+
+		this.db = db;
+		this.identityManager = identityManager;
+		this.privateGroupFactory = privateGroupFactory;
+	}
+
+	@NotNull
+	@Override
+	public ClientId getClientId() {
+		return CLIENT_ID;
+	}
+
+	@Override
+	public void addLocalMessage(GroupMessage m) throws DbException {
+		try {
+			BdfDictionary meta = new BdfDictionary();
+			clientHelper.addLocalMessage(m.getMessage(), meta, true);
+		} catch (FormatException e) {
+			throw new DbException(e);
+		}
+	}
+
+	@NotNull
+	@Override
+	public PrivateGroup getPrivateGroup(GroupId g) throws DbException {
+		Author a = identityManager.getLocalAuthor();
+		return privateGroupFactory.createPrivateGroup("todo", a);
+	}
+
+	@NotNull
+	@Override
+	public PrivateGroup getPrivateGroup(Transaction txn, GroupId g)
+			throws DbException {
+		Author a = identityManager.getLocalAuthor(txn);
+		return privateGroupFactory.createPrivateGroup("todo", a);
+	}
+
+	@NotNull
+	@Override
+	public Collection<PrivateGroup> getPrivateGroups() throws DbException {
+		return Collections.emptyList();
+	}
+
+	@NotNull
+	@Override
+	public String getMessageBody(MessageId m) throws DbException {
+		return "empty";
+	}
+
+	@NotNull
+	@Override
+	public Collection<GroupMessageHeader> getHeaders(GroupId g)
+			throws DbException {
+
+		return Collections.emptyList();
+	}
+
+	@Override
+	public void setReadFlag(MessageId m, boolean read) throws DbException {
+		try {
+			BdfDictionary meta = new BdfDictionary();
+			meta.put(KEY_READ, read);
+			clientHelper.mergeMessageMetadata(m, meta);
+		} catch (FormatException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
+			BdfDictionary meta) throws DbException, FormatException {
+
+		return true;
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java
new file mode 100644
index 0000000000..562b95c308
--- /dev/null
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java
@@ -0,0 +1,68 @@
+package org.briarproject.privategroup;
+
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.privategroup.GroupMessageFactory;
+import org.briarproject.api.privategroup.PrivateGroupFactory;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.sync.GroupFactory;
+import org.briarproject.api.sync.ValidationManager;
+import org.briarproject.api.system.Clock;
+
+import java.security.SecureRandom;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class PrivateGroupModule {
+
+	public static class EagerSingletons {
+		@Inject
+		GroupMessageValidator groupMessageValidator;
+	}
+
+	@Provides
+	@Singleton
+	PrivateGroupManager provideForumManager(
+			PrivateGroupManagerImpl groupManager,
+			ValidationManager validationManager) {
+
+		validationManager
+				.registerIncomingMessageHook(groupManager.getClientId(),
+						groupManager);
+
+		return groupManager;
+	}
+
+	@Provides
+	PrivateGroupFactory providePrivateGroupFactory(
+			PrivateGroupFactoryImpl privateGroupFactory) {
+		return privateGroupFactory;
+	}
+
+	@Provides
+	GroupMessageFactory provideGroupMessageFactory(
+			GroupMessageFactoryImpl groupMessageFactory) {
+		return groupMessageFactory;
+	}
+
+	@Provides
+	@Singleton
+	GroupMessageValidator provideGroupMessageValidator(
+			ValidationManager validationManager, CryptoComponent crypto,
+			AuthorFactory authorFactory, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		GroupMessageValidator validator = new GroupMessageValidator(crypto,
+				authorFactory, clientHelper, metadataEncoder, clock);
+		validationManager.registerMessageValidator(
+				PrivateGroupManagerImpl.CLIENT_ID, validator);
+		return validator;
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index 8c7e60549a..cdf7d4eabd 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -4,7 +4,7 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager.AddContactHook;
@@ -40,19 +40,19 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 
 	private final DatabaseComponent db;
 	private final ClientHelper clientHelper;
-	private final PrivateGroupFactory privateGroupFactory;
+	private final ContactGroupFactory contactGroupFactory;
 	private final Clock clock;
 	private final Group localGroup;
 
 	@Inject
 	TransportPropertyManagerImpl(DatabaseComponent db,
-			ClientHelper clientHelper, PrivateGroupFactory privateGroupFactory,
+			ClientHelper clientHelper, ContactGroupFactory contactGroupFactory,
 			Clock clock) {
 		this.db = db;
 		this.clientHelper = clientHelper;
-		this.privateGroupFactory = privateGroupFactory;
+		this.contactGroupFactory = contactGroupFactory;
 		this.clock = clock;
-		localGroup = privateGroupFactory.createLocalGroup(CLIENT_ID);
+		localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
 	}
 
 	@Override
@@ -232,7 +232,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	}
 
 	private Group getContactGroup(Contact c) {
-		return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
+		return contactGroupFactory.createContactGroup(CLIENT_ID, c);
 	}
 
 	private void storeMessage(Transaction txn, GroupId g, TransportId t,
diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
index 7c1ce43bbb..32314ec730 100644
--- a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java
@@ -11,7 +11,7 @@ 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.ContactGroupFactory;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -68,10 +68,10 @@ class BlogSharingManagerImpl extends
 			BlogManager blogManager, ClientHelper clientHelper, Clock clock,
 			DatabaseComponent db, MessageQueueManager messageQueueManager,
 			MetadataEncoder metadataEncoder, MetadataParser metadataParser,
-			PrivateGroupFactory privateGroupFactory, SecureRandom random) {
+			ContactGroupFactory contactGroupFactory, SecureRandom random) {
 
 		super(db, messageQueueManager, clientHelper, metadataParser,
-				metadataEncoder, random, privateGroupFactory, clock);
+				metadataEncoder, random, contactGroupFactory, clock);
 
 		this.blogManager = blogManager;
 		sFactory = new SFactory(authorFactory, blogFactory, blogManager);
diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
index eb07240015..89bf145cc7 100644
--- a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java
@@ -3,7 +3,7 @@ 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.ContactGroupFactory;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.data.BdfDictionary;
@@ -59,10 +59,10 @@ class ForumSharingManagerImpl extends
 			MessageQueueManager messageQueueManager,
 			MetadataEncoder metadataEncoder,
 			MetadataParser metadataParser,
-			PrivateGroupFactory privateGroupFactory,
+			ContactGroupFactory contactGroupFactory,
 			SecureRandom random) {
 		super(db, messageQueueManager, clientHelper, metadataParser,
-				metadataEncoder, random, privateGroupFactory, clock);
+				metadataEncoder, random, contactGroupFactory, clock);
 
 		sFactory = new SFactory(forumFactory, forumManager);
 		iFactory = new IFactory();
diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
index 55886301c4..db29f569e4 100644
--- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java
@@ -5,7 +5,7 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
@@ -97,14 +97,14 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
 	private final MessageQueueManager messageQueueManager;
 	private final MetadataEncoder metadataEncoder;
 	private final SecureRandom random;
-	private final PrivateGroupFactory privateGroupFactory;
+	private final ContactGroupFactory contactGroupFactory;
 	private final Clock clock;
 	private final Group localGroup;
 
 	SharingManagerImpl(DatabaseComponent db,
 			MessageQueueManager messageQueueManager, ClientHelper clientHelper,
 			MetadataParser metadataParser, MetadataEncoder metadataEncoder,
-			SecureRandom random, PrivateGroupFactory privateGroupFactory,
+			SecureRandom random, ContactGroupFactory contactGroupFactory,
 			Clock clock) {
 
 		super(clientHelper, metadataParser);
@@ -112,9 +112,9 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
 		this.messageQueueManager = messageQueueManager;
 		this.metadataEncoder = metadataEncoder;
 		this.random = random;
-		this.privateGroupFactory = privateGroupFactory;
+		this.contactGroupFactory = contactGroupFactory;
 		this.clock = clock;
-		localGroup = privateGroupFactory.createLocalGroup(getClientId());
+		localGroup = contactGroupFactory.createLocalGroup(getClientId());
 	}
 
 	public abstract ClientId getClientId();
@@ -903,7 +903,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
 	}
 
 	private Group getContactGroup(Contact c) {
-		return privateGroupFactory.createPrivateGroup(getClientId(), c);
+		return contactGroupFactory.createContactGroup(getClientId(), c);
 	}
 
 	private ContactId getContactId(Transaction txn, GroupId contactGroupId)
diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java
index 222ea050ef..23147e045b 100644
--- a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java
@@ -5,7 +5,7 @@ import org.briarproject.TestUtils;
 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.ContactGroupFactory;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.data.BdfDictionary;
@@ -55,7 +55,7 @@ public class IntroductionManagerImplTest extends BriarTestCase {
 	final IntroducerManager introducerManager;
 	final IntroduceeManager introduceeManager;
 	final DatabaseComponent db;
-	final PrivateGroupFactory privateGroupFactory;
+	final ContactGroupFactory contactGroupFactory;
 	final ClientHelper clientHelper;
 	final MetadataEncoder metadataEncoder;
 	final MessageQueueManager messageQueueManager;
@@ -109,7 +109,7 @@ public class IntroductionManagerImplTest extends BriarTestCase {
 		introducerManager = context.mock(IntroducerManager.class);
 		introduceeManager = context.mock(IntroduceeManager.class);
 		db = context.mock(DatabaseComponent.class);
-		privateGroupFactory = context.mock(PrivateGroupFactory.class);
+		contactGroupFactory = context.mock(ContactGroupFactory.class);
 		clientHelper = context.mock(ClientHelper.class);
 		metadataEncoder =
 				context.mock(MetadataEncoder.class);
-- 
GitLab