diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index 6081f1da9b5172bc1c1a6d2e7a74c47f37b6ad27..a436f9430fc9dc9d0e6b3582b8ed87f928160220 100644 --- a/briar-android/src/org/briarproject/android/AndroidComponent.java +++ b/briar-android/src/org/briarproject/android/AndroidComponent.java @@ -34,6 +34,8 @@ import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.PrivateMessageFactory; import org.briarproject.api.plugins.ConnectionRegistry; import org.briarproject.api.plugins.PluginManager; +import org.briarproject.api.privategroup.GroupMessageFactory; +import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.settings.SettingsManager; @@ -99,6 +101,10 @@ public interface AndroidComponent extends CoreEagerSingletons { GroupInvitationManager groupInvitationManager(); + PrivateGroupFactory privateGroupFactory(); + + GroupMessageFactory groupMessageFactory(); + ForumManager forumManager(); ForumSharingManager forumSharingManager(); diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java index 8a6644efddaebff7b71a226c7631604e28b6f284..24b2c6fb2945f683d531d4b05d068656bcfa55b5 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -15,6 +15,7 @@ import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.privategroup.GroupMessage; +import org.briarproject.api.privategroup.GroupMessageFactory; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupManager; @@ -35,16 +36,19 @@ public class GroupControllerImpl extends Logger.getLogger(GroupControllerImpl.class.getName()); private final PrivateGroupManager privateGroupManager; + private final GroupMessageFactory groupMessageFactory; @Inject GroupControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, - PrivateGroupManager privateGroupManager, EventBus eventBus, + PrivateGroupManager privateGroupManager, + GroupMessageFactory groupMessageFactory, EventBus eventBus, AndroidNotificationManager notificationManager, Clock clock) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, eventBus, notificationManager, clock); this.privateGroupManager = privateGroupManager; + this.groupMessageFactory = groupMessageFactory; } @Override @@ -101,8 +105,10 @@ public class GroupControllerImpl extends @Override protected GroupMessage createLocalMessage(String body, long timestamp, @Nullable MessageId parentId, LocalAuthor author) { - return privateGroupManager.createLocalMessage(getGroupId(), body, - timestamp, parentId, author); + MessageId previousMsgId = null; // TODO + return groupMessageFactory + .createGroupMessage(getGroupId(), timestamp, parentId, + author, body, previousMsgId); } @Override diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java index a35c2ac20435a7486ecbc1407989c3216d1b42e1..db661c43da5863f4b5a5805e7434ab88e2775e8b 100644 --- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java @@ -3,11 +3,19 @@ package org.briarproject.android.privategroup.creation; import org.briarproject.android.controller.DbControllerImpl; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.GroupMessage; +import org.briarproject.api.privategroup.GroupMessageFactory; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.sync.GroupId; +import org.briarproject.api.system.Clock; import java.util.Collection; import java.util.concurrent.Executor; @@ -23,25 +31,81 @@ public class CreateGroupControllerImpl extends DbControllerImpl private static final Logger LOG = Logger.getLogger(CreateGroupControllerImpl.class.getName()); + private final IdentityManager identityManager; + private final PrivateGroupFactory groupFactory; + private final GroupMessageFactory groupMessageFactory; private final PrivateGroupManager groupManager; + private final Clock clock; + @CryptoExecutor + private final Executor cryptoExecutor; @Inject CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor, - LifecycleManager lifecycleManager, - PrivateGroupManager groupManager) { + @CryptoExecutor Executor cryptoExecutor, + LifecycleManager lifecycleManager, IdentityManager identityManager, + PrivateGroupFactory groupFactory, + GroupMessageFactory groupMessageFactory, + PrivateGroupManager groupManager, Clock clock) { super(dbExecutor, lifecycleManager); + this.identityManager = identityManager; + this.groupFactory = groupFactory; + this.groupMessageFactory = groupMessageFactory; this.groupManager = groupManager; + this.clock = clock; + this.cryptoExecutor = cryptoExecutor; } @Override public void createGroup(final String name, final ResultExceptionHandler<GroupId, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + LocalAuthor author = identityManager.getLocalAuthor(); + createGroupAndMessages(author, name, handler); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + private void createGroupAndMessages(final LocalAuthor author, + final String name, + final ResultExceptionHandler<GroupId, DbException> handler) { + cryptoExecutor.execute(new Runnable() { + @Override + public void run() { + LOG.info("Creating group..."); + PrivateGroup group = + groupFactory.createPrivateGroup(name, author); + LOG.info("Creating new member announcement..."); + GroupMessage newMemberMsg = groupMessageFactory + .createNewMemberMessage(group.getId(), + clock.currentTimeMillis(), author, author); + LOG.info("Creating new join announcement..."); + GroupMessage joinMsg = groupMessageFactory + .createJoinMessage(group.getId(), + newMemberMsg.getMessage().getTimestamp(), + author, newMemberMsg.getMessage().getId()); + storeGroup(group, newMemberMsg, joinMsg, handler); + } + }); + } + + private void storeGroup(final PrivateGroup group, + final GroupMessage newMemberMsg, final GroupMessage joinMsg, + final ResultExceptionHandler<GroupId, DbException> handler) { runOnDbThread(new Runnable() { @Override public void run() { LOG.info("Adding group to database..."); try { - handler.onResult(groupManager.addPrivateGroup(name)); + groupManager.addPrivateGroup(group, newMemberMsg, joinMsg); + handler.onResult(group.getId()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java index 80a5b44ea2ab4a968dc897dcd5d6c6f517c7b84a..4f348280edee261b2784b4256cb6c87c263a56dd 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java @@ -44,6 +44,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T Logger.getLogger(ThreadListControllerImpl.class.getName()); private final IdentityManager identityManager; + @CryptoExecutor private final Executor cryptoExecutor; protected final AndroidNotificationManager notificationManager; private final EventBus eventBus; diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index f2253c99a7c0c1999c1910146602e586ba4cf153..b01cd88c7f6d03c41d2593aebe5229aa326850fe 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -3,57 +3,57 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; -import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collection; public interface PrivateGroupManager extends MessageTracker { /** Returns the unique ID of the private group client. */ - @NotNull ClientId getClientId(); - /** Adds a new private group. */ - GroupId addPrivateGroup(String name) throws DbException; + /** + * Adds a new private group and joins it. + * + * @param group The private group to add + * @param newMemberMsg The creator's message announcing the first new member + * @param joinMsg The first new member's join message + */ + void addPrivateGroup(PrivateGroup group, GroupMessage newMemberMsg, + GroupMessage joinMsg) throws DbException; /** Removes a dissolved private group. */ void removePrivateGroup(GroupId g) throws DbException; - /** Creates a local group message. */ - GroupMessage createLocalMessage(GroupId groupId, String body, - long timestamp, @Nullable MessageId parentId, LocalAuthor author); + /** Gets the MessageId of the */ + MessageId getPreviousMsgId(GroupId g) throws DbException; + + /** Returns the timestamp of the message with the given ID */ + long getMessageTimestamp(MessageId id) throws DbException; /** Stores (and sends) a local group message. */ GroupMessageHeader 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 true if the private group has been dissolved. */ boolean isDissolved(GroupId g) 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; } diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index b392abb8f181e3d35d938b6900b5fc3db3e4c8f8..0f28ce927165b868bd784867d101856f39099e26 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -11,4 +11,6 @@ interface Constants { String KEY_AUTHOR_NAME = "authorName"; String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey"; + // Messaging Group Metadata + String KEY_PREVIOUS_MSG_ID = "previousMsgId"; } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index ee861267e916d5f870017ae105e4b2ac1de228f6..78c2693f6968e9859afa9fdc80fb878627b89412 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -3,16 +3,15 @@ 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.BdfEntry; 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.IdentityManager; -import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.privategroup.GroupMessage; -import org.briarproject.api.privategroup.GroupMessageFactory; import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.privategroup.MessageType; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; @@ -21,13 +20,10 @@ import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; -import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfIncomingMessageHook; import org.briarproject.util.StringUtils; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -36,6 +32,12 @@ import java.util.logging.Logger; import javax.inject.Inject; import static org.briarproject.api.identity.Author.Status.OURSELVES; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_NAME; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_PUBLIC_KEY; +import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; +import static org.briarproject.privategroup.Constants.KEY_READ; +import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.Constants.KEY_TYPE; public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements PrivateGroupManager { @@ -46,23 +48,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements StringUtils.fromHexString("5072697661746547726f75704d616e61" + "67657220627920546f727374656e2047")); - private final IdentityManager identityManager; private final PrivateGroupFactory privateGroupFactory; - private final GroupMessageFactory groupMessageFactory; - private final Clock clock; @Inject PrivateGroupManagerImpl(ClientHelper clientHelper, MetadataParser metadataParser, DatabaseComponent db, - IdentityManager identityManager, - PrivateGroupFactory privateGroupFactory, - GroupMessageFactory groupMessageFactory, Clock clock) { + PrivateGroupFactory privateGroupFactory) { super(db, clientHelper, metadataParser); - this.identityManager = identityManager; this.privateGroupFactory = privateGroupFactory; - this.groupMessageFactory = groupMessageFactory; - this.clock = clock; } @NotNull @@ -72,36 +66,81 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } @Override - public GroupId addPrivateGroup(String name) throws DbException { - PrivateGroup group; + public void addPrivateGroup(PrivateGroup group, + GroupMessage newMemberMsg, GroupMessage joinMsg) + throws DbException { Transaction txn = db.startTransaction(false); try { - LocalAuthor a = identityManager.getLocalAuthor(txn); - group = privateGroupFactory.createPrivateGroup(name, a); db.addGroup(txn, group.getGroup()); + announceNewMember(txn, newMemberMsg); + joinPrivateGroup(txn, joinMsg); txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); } finally { db.endTransaction(txn); } - return group.getId(); + } + + private void announceNewMember(Transaction txn, GroupMessage m) + throws DbException, FormatException { + BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TYPE, MessageType.NEW_MEMBER.getInt()); + clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); + } + + private void joinPrivateGroup(Transaction txn, GroupMessage m) + throws DbException, FormatException { + BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TYPE, MessageType.JOIN.getInt()); + addMessageMetadata(meta, m, true); + clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); + trackOutgoingMessage(txn, m.getMessage()); + setPreviousMsgId(txn, m.getMessage().getGroupId(), + m.getMessage().getId()); } @Override public void removePrivateGroup(GroupId g) throws DbException { - + // TODO } @Override - public GroupMessage createLocalMessage(GroupId groupId, String body, - long timestamp, @Nullable MessageId parentId, LocalAuthor author) { + public MessageId getPreviousMsgId(GroupId g) throws DbException { + MessageId previousMsgId; + Transaction txn = db.startTransaction(true); + try { + previousMsgId = getPreviousMsgId(txn, g); + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + return previousMsgId; + } + + private MessageId getPreviousMsgId(Transaction txn, GroupId g) + throws DbException, FormatException { + BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g); + byte[] previousMsgIdBytes = d.getOptionalRaw(KEY_PREVIOUS_MSG_ID); + if (previousMsgIdBytes == null) throw new DbException(); + return new MessageId(previousMsgIdBytes); + } + + private void setPreviousMsgId(Transaction txn, GroupId g, + MessageId previousMsgId) throws DbException, FormatException { + BdfDictionary d = BdfDictionary + .of(new BdfEntry(KEY_PREVIOUS_MSG_ID, previousMsgId)); + clientHelper.mergeGroupMetadata(txn, g, d); + } + + public long getMessageTimestamp(MessageId id) throws DbException { try { - return groupMessageFactory - .createGroupMessage(groupId, timestamp, parentId, author, - body); + BdfDictionary d = clientHelper.getMessageMetadataAsDictionary(id); + return d.getLong(KEY_TIMESTAMP); } catch (FormatException e) { - throw new RuntimeException(e); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); + throw new DbException(e); } } @@ -111,6 +150,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements Transaction txn = db.startTransaction(false); try { BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TYPE, MessageType.POST.getInt()); + addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); trackOutgoingMessage(txn, m.getMessage()); txn.setComplete(); @@ -121,7 +162,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } return new GroupMessageHeader(m.getMessage().getGroupId(), m.getMessage().getId(), m.getParent(), - m.getMessage().getTimestamp(), m.getAuthor(), OURSELVES, true); + m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true); } @NotNull @@ -198,7 +239,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements trackIncomingMessage(txn, m); - // TODO POST timestamp must be greater than the timestamps of the parent post, if any, and the member's previous message // TODO JOIN timestamp must be equal to the timestamp of the new member message. @@ -207,4 +247,12 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return true; } + private void addMessageMetadata(BdfDictionary meta, GroupMessage m, + boolean read) { + meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp()); + meta.put(KEY_READ, read); + meta.put(KEY_AUTHOR_NAME, m.getMember().getName()); + meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey()); + } + }