From 8dc529cc3f5b987021422c2930f1e966bb49ed4d Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Oct 2016 13:35:27 -0200 Subject: [PATCH 01/11] Move validator's signature verification into ClientHelper --- .../api/clients/ClientHelper.java | 5 ++ .../briarproject/blogs/BlogPostValidator.java | 38 ++----------- .../org/briarproject/blogs/BlogsModule.java | 15 +++-- .../clients/ClientHelperImpl.java | 24 ++++++++ .../org/briarproject/forum/ForumModule.java | 10 ++-- .../forum/ForumPostValidator.java | 33 ++--------- .../blogs/BlogPostValidatorTest.java | 55 ++++--------------- 7 files changed, 64 insertions(+), 116 deletions(-) diff --git a/briar-api/src/org/briarproject/api/clients/ClientHelper.java b/briar-api/src/org/briarproject/api/clients/ClientHelper.java index 5aeffaa21..4622431d4 100644 --- a/briar-api/src/org/briarproject/api/clients/ClientHelper.java +++ b/briar-api/src/org/briarproject/api/clients/ClientHelper.java @@ -6,6 +6,7 @@ import org.briarproject.api.data.BdfList; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; @@ -81,4 +82,8 @@ public interface ClientHelper { byte[] sign(BdfList toSign, byte[] privateKey) throws FormatException, GeneralSecurityException; + + void verifySignature(byte[] sig, byte[] publicKey, BdfList signed) + throws InvalidMessageException; + } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java index 29908c6d3..75b4797d0 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -6,10 +6,6 @@ import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.MessageType; import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.crypto.CryptoComponent; -import org.briarproject.api.crypto.KeyParser; -import org.briarproject.api.crypto.PublicKey; -import org.briarproject.api.crypto.Signature; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; @@ -24,7 +20,6 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfMessageValidator; -import java.security.GeneralSecurityException; import java.util.Collection; import java.util.Collections; @@ -48,18 +43,15 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH class BlogPostValidator extends BdfMessageValidator { - private final CryptoComponent crypto; private final GroupFactory groupFactory; private final MessageFactory messageFactory; private final BlogFactory blogFactory; - BlogPostValidator(CryptoComponent crypto, GroupFactory groupFactory, - MessageFactory messageFactory, BlogFactory blogFactory, - ClientHelper clientHelper, MetadataEncoder metadataEncoder, - Clock clock) { + BlogPostValidator(GroupFactory groupFactory, MessageFactory messageFactory, + BlogFactory blogFactory, ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { super(clientHelper, metadataEncoder, clock); - this.crypto = crypto; this.groupFactory = groupFactory; this.messageFactory = messageFactory; this.blogFactory = blogFactory; @@ -109,7 +101,7 @@ class BlogPostValidator extends BdfMessageValidator { BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), postBody); Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter Author a = b.getAuthor(); - verifySignature(sig, a.getPublicKey(), signed); + clientHelper.verifySignature(sig, a.getPublicKey(), signed); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); @@ -150,7 +142,7 @@ class BlogPostValidator extends BdfMessageValidator { currentId); Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter Author a = b.getAuthor(); - verifySignature(sig, a.getPublicKey(), signed); + clientHelper.verifySignature(sig, a.getPublicKey(), signed); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); @@ -267,26 +259,6 @@ class BlogPostValidator extends BdfMessageValidator { return new BdfMessageContext(meta, dependencies); } - private void verifySignature(byte[] sig, byte[] publicKey, BdfList signed) - throws InvalidMessageException { - try { - // Parse the public key - KeyParser keyParser = crypto.getSignatureKeyParser(); - PublicKey key = keyParser.parsePublicKey(publicKey); - // Verify the signature - Signature signature = crypto.getSignature(); - signature.initVerify(key); - signature.update(clientHelper.toByteArray(signed)); - if (!signature.verify(sig)) { - throw new InvalidMessageException("Invalid signature"); - } - } catch (GeneralSecurityException e) { - throw new InvalidMessageException("Invalid public key"); - } catch (FormatException e) { - throw new InvalidMessageException(e); - } - } - static BdfDictionary authorToBdfDictionary(Author a) { return BdfDictionary.of( new BdfEntry(KEY_AUTHOR_ID, a.getId()), diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java index 91cc68c7c..e8927d5af 100644 --- a/briar-core/src/org/briarproject/blogs/BlogsModule.java +++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java @@ -5,7 +5,6 @@ import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogPostFactory; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.ContactManager; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; @@ -64,14 +63,14 @@ public class BlogsModule { @Provides @Singleton BlogPostValidator provideBlogPostValidator( - ValidationManager validationManager, CryptoComponent crypto, - GroupFactory groupFactory, MessageFactory messageFactory, - BlogFactory blogFactory, ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { + ValidationManager validationManager, GroupFactory groupFactory, + MessageFactory messageFactory, BlogFactory blogFactory, + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock) { - BlogPostValidator validator = new BlogPostValidator(crypto, - groupFactory, messageFactory, blogFactory, clientHelper, - metadataEncoder, clock); + BlogPostValidator validator = new BlogPostValidator(groupFactory, + messageFactory, blogFactory, clientHelper, metadataEncoder, + clock); validationManager.registerMessageValidator(CLIENT_ID, validator); return validator; diff --git a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java index d7a983b0a..612f80e81 100644 --- a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java +++ b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java @@ -5,6 +5,7 @@ import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.KeyParser; import org.briarproject.api.crypto.PrivateKey; +import org.briarproject.api.crypto.PublicKey; import org.briarproject.api.crypto.Signature; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; @@ -19,6 +20,7 @@ import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Transaction; import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageFactory; import org.briarproject.api.sync.MessageId; @@ -320,4 +322,26 @@ class ClientHelperImpl implements ClientHelper { signature.update(toByteArray(toSign)); return signature.sign(); } + + @Override + public void verifySignature(byte[] sig, byte[] publicKey, BdfList signed) + throws InvalidMessageException { + try { + // Parse the public key + KeyParser keyParser = cryptoComponent.getSignatureKeyParser(); + PublicKey key = keyParser.parsePublicKey(publicKey); + // Verify the signature + Signature signature = cryptoComponent.getSignature(); + signature.initVerify(key); + signature.update(toByteArray(signed)); + if (!signature.verify(sig)) { + throw new InvalidMessageException("Invalid signature"); + } + } catch (GeneralSecurityException e) { + throw new InvalidMessageException("Invalid public key"); + } catch (FormatException e) { + throw new InvalidMessageException(e); + } + } + } diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java index 40bfd0138..eeafb54df 100644 --- a/briar-core/src/org/briarproject/forum/ForumModule.java +++ b/briar-core/src/org/briarproject/forum/ForumModule.java @@ -54,11 +54,11 @@ public class ForumModule { @Provides @Singleton ForumPostValidator provideForumPostValidator( - ValidationManager validationManager, CryptoComponent crypto, - AuthorFactory authorFactory, ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { - ForumPostValidator validator = new ForumPostValidator(crypto, - authorFactory, clientHelper, metadataEncoder, clock); + ValidationManager validationManager, AuthorFactory authorFactory, + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock) { + ForumPostValidator validator = new ForumPostValidator(authorFactory, + clientHelper, metadataEncoder, clock); validationManager.registerMessageValidator( ForumManagerImpl.CLIENT_ID, validator); return validator; diff --git a/briar-core/src/org/briarproject/forum/ForumPostValidator.java b/briar-core/src/org/briarproject/forum/ForumPostValidator.java index 258ac9bb5..f25181f1c 100644 --- a/briar-core/src/org/briarproject/forum/ForumPostValidator.java +++ b/briar-core/src/org/briarproject/forum/ForumPostValidator.java @@ -4,10 +4,6 @@ import org.briarproject.api.FormatException; import org.briarproject.api.UniqueId; import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.crypto.CryptoComponent; -import org.briarproject.api.crypto.KeyParser; -import org.briarproject.api.crypto.PublicKey; -import org.briarproject.api.crypto.Signature; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataEncoder; @@ -20,7 +16,6 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfMessageValidator; -import java.security.GeneralSecurityException; import java.util.Collection; import java.util.Collections; @@ -32,14 +27,11 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH class ForumPostValidator extends BdfMessageValidator { - private final CryptoComponent crypto; private final AuthorFactory authorFactory; - ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory, - ClientHelper clientHelper, MetadataEncoder metadataEncoder, - Clock clock) { + ForumPostValidator(AuthorFactory authorFactory, ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { super(clientHelper, metadataEncoder, clock); - this.crypto = crypto; this.authorFactory = authorFactory; } @@ -81,23 +73,10 @@ class ForumPostValidator extends BdfMessageValidator { } // Verify the signature, if any if (author != null) { - try { - // Parse the public key - KeyParser keyParser = crypto.getSignatureKeyParser(); - PublicKey key = keyParser.parsePublicKey(author.getPublicKey()); - // Serialise the data to be signed - BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent, - authorList, contentType, forumPostBody); - // Verify the signature - Signature signature = crypto.getSignature(); - signature.initVerify(key); - signature.update(clientHelper.toByteArray(signed)); - if (!signature.verify(sig)) { - throw new InvalidMessageException("Invalid signature"); - } - } catch (GeneralSecurityException e) { - throw new InvalidMessageException("Invalid public key"); - } + // Serialise the data to be signed + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent, + authorList, contentType, forumPostBody); + clientHelper.verifySignature(sig, author.getPublicKey(), signed); } // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); diff --git a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java index bdbcc2398..afb1b90b9 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java @@ -7,9 +7,6 @@ import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.crypto.CryptoComponent; -import org.briarproject.api.crypto.KeyParser; -import org.briarproject.api.crypto.PublicKey; -import org.briarproject.api.crypto.Signature; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; @@ -20,7 +17,6 @@ import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupFactory; import org.briarproject.api.sync.GroupId; -import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageFactory; import org.briarproject.api.sync.MessageId; @@ -37,9 +33,9 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT; +import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID; -import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.MessageType.COMMENT; @@ -94,9 +90,8 @@ public class BlogPostValidatorTest extends BriarTestCase { message = new Message(messageId, group.getId(), timestamp, raw); MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); - validator = new BlogPostValidator(cryptoComponent, groupFactory, - messageFactory, blogFactory, clientHelper, metadataEncoder, - clock); + validator = new BlogPostValidator(groupFactory, messageFactory, + blogFactory, clientHelper, metadataEncoder, clock); context.assertIsSatisfied(); } @@ -108,7 +103,7 @@ public class BlogPostValidatorTest extends BriarTestCase { BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), body); - expectCrypto(signed, sigBytes, true); + expectCrypto(signed, sigBytes); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); @@ -135,18 +130,6 @@ public class BlogPostValidatorTest extends BriarTestCase { validator.validateMessage(message, group, m).getDictionary(); } - @Test(expected = InvalidMessageException.class) - public void testValidateBlogPostWithBadSignature() - throws IOException, GeneralSecurityException { - final byte[] sigBytes = TestUtils.getRandomBytes(42); - BdfList m = BdfList.of(POST.getInt(), body, sigBytes); - - BdfList signed = - BdfList.of(blog.getId(), message.getTimestamp(), body); - expectCrypto(signed, sigBytes, false); - validator.validateMessage(message, group, m).getDictionary(); - } - @Test public void testValidateProperBlogComment() throws IOException, GeneralSecurityException { @@ -162,7 +145,7 @@ public class BlogPostValidatorTest extends BriarTestCase { BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), comment, pOriginalId, currentId); - expectCrypto(signed, sigBytes, true); + expectCrypto(signed, sigBytes); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); @@ -189,7 +172,7 @@ public class BlogPostValidatorTest extends BriarTestCase { BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), null, originalId, currentId); - expectCrypto(signed, sigBytes, true); + expectCrypto(signed, sigBytes); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); @@ -208,7 +191,7 @@ public class BlogPostValidatorTest extends BriarTestCase { BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), body); - expectCrypto(signed, sigBytes, true); + expectCrypto(signed, sigBytes); final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes); final byte[] originalBody = TestUtils.getRandomBytes(42); @@ -247,7 +230,7 @@ public class BlogPostValidatorTest extends BriarTestCase { BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), comment, originalId, oldId); - expectCrypto(signed, sigBytes, true); + expectCrypto(signed, sigBytes); final BdfList originalList = BdfList.of(COMMENT.getInt(), comment, originalId, oldId, sigBytes); @@ -275,27 +258,13 @@ public class BlogPostValidatorTest extends BriarTestCase { context.assertIsSatisfied(); } - private void expectCrypto(final BdfList signed, final byte[] sig, - final boolean pass) throws IOException, GeneralSecurityException { - final Signature signature = context.mock(Signature.class); - final KeyParser keyParser = context.mock(KeyParser.class); - final PublicKey publicKey = context.mock(PublicKey.class); - + private void expectCrypto(final BdfList signed, final byte[] sig) + throws IOException, GeneralSecurityException { context.checking(new Expectations() {{ oneOf(blogFactory).parseBlog(group, ""); will(returnValue(blog)); - oneOf(cryptoComponent).getSignatureKeyParser(); - will(returnValue(keyParser)); - oneOf(keyParser).parsePublicKey(blog.getAuthor().getPublicKey()); - will(returnValue(publicKey)); - oneOf(cryptoComponent).getSignature(); - will(returnValue(signature)); - oneOf(signature).initVerify(publicKey); - oneOf(clientHelper).toByteArray(signed); - will(returnValue(sig)); - oneOf(signature).update(sig); - oneOf(signature).verify(sig); - will(returnValue(pass)); + oneOf(clientHelper) + .verifySignature(sig, author.getPublicKey(), signed); }}); } From a6e38271275141a8a6dab3111de0fc7c480b8926 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Oct 2016 14:28:21 -0200 Subject: [PATCH 02/11] Implement first prototype of GroupMessageValidator --- .../api/privategroup/MessageType.java | 30 ++++ .../briarproject/privategroup/Constants.java | 8 +- .../privategroup/GroupMessageValidator.java | 166 +++++++++++++++++- .../privategroup/PrivateGroupManagerImpl.java | 6 + .../privategroup/PrivateGroupModule.java | 10 +- 5 files changed, 205 insertions(+), 15 deletions(-) create mode 100644 briar-api/src/org/briarproject/api/privategroup/MessageType.java diff --git a/briar-api/src/org/briarproject/api/privategroup/MessageType.java b/briar-api/src/org/briarproject/api/privategroup/MessageType.java new file mode 100644 index 000000000..afe6a07d1 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/MessageType.java @@ -0,0 +1,30 @@ +package org.briarproject.api.privategroup; + +public enum MessageType { + NEW_MEMBER(0), + JOIN(1), + POST(2); + + int value; + + MessageType(int value) { + this.value = value; + } + + public static MessageType valueOf(int value) { + switch (value) { + case 0: + return NEW_MEMBER; + case 1: + return JOIN; + case 2: + return POST; + default: + throw new IllegalArgumentException(); + } + } + + public int getInt() { + return value; + } +} \ No newline at end of file diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index 7a1a4495b..b392abb8f 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -1,8 +1,14 @@ package org.briarproject.privategroup; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; + interface Constants { // Database keys - String KEY_READ = "read"; + String KEY_TYPE = "type"; + String KEY_TIMESTAMP = "timestamp"; + String KEY_READ = MSG_KEY_READ; + String KEY_AUTHOR_NAME = "authorName"; + String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey"; } diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 75a8913c0..1bd94100b 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -3,11 +3,12 @@ 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.privategroup.MessageType; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; @@ -15,29 +16,178 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfMessageValidator; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; +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_READ; +import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.Constants.KEY_TYPE; + class GroupMessageValidator extends BdfMessageValidator { - private final CryptoComponent crypto; - private final AuthorFactory authorFactory; + private final PrivateGroupFactory groupFactory; - GroupMessageValidator(CryptoComponent crypto, AuthorFactory authorFactory, + GroupMessageValidator(PrivateGroupFactory groupFactory, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { super(clientHelper, metadataEncoder, clock); - this.crypto = crypto; - this.authorFactory = authorFactory; + this.groupFactory = groupFactory; } @Override protected BdfMessageContext validateMessage(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { + checkSize(body, 4, 7); + + // message type (int) + int type = body.getLong(0).intValue(); + body.removeElementAt(0); + + // member_name (string) + String member_name = body.getString(0); + checkLength(member_name, 1, MAX_AUTHOR_NAME_LENGTH); + + // member_public_key (raw) + byte[] member_public_key = body.getRaw(1); + checkLength(member_public_key, 1, MAX_PUBLIC_KEY_LENGTH); + + BdfMessageContext c; + switch (MessageType.valueOf(type)) { + case NEW_MEMBER: + c = validateNewMember(m, g, body, member_name, + member_public_key); + addMessageMetadata(c, member_name, member_public_key, + m.getTimestamp()); + break; + case JOIN: + c = validateJoin(m, g, body, member_name, member_public_key); + addMessageMetadata(c, member_name, member_public_key, + m.getTimestamp()); + break; + case POST: + c = validatePost(m, g, body, member_name, member_public_key); + break; + default: + throw new InvalidMessageException("Unknown Message Type"); + } + c.getDictionary().put(KEY_TYPE, type); + return c; + } + + private BdfMessageContext validateNewMember(Message m, Group g, + BdfList body, String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with three elements + checkSize(body, 3); + + // signature (raw) + // signature with the creator's private key over a list with 4 elements + byte[] signature = body.getRaw(2); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key); + PrivateGroup group = groupFactory.parsePrivateGroup(g); + byte[] creatorPublicKey = group.getAuthor().getPublicKey(); + clientHelper.verifySignature(signature, creatorPublicKey, signed); + + // Return the metadata and no dependencies + BdfDictionary meta = new BdfDictionary(); + return new BdfMessageContext(meta); + } + + private BdfMessageContext validateJoin(Message m, Group g, BdfList body, + String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with four elements + checkSize(body, 4); + + // new_member_id (raw) + // the identifier of a new member message + // with the same member_name and member_public_key + byte[] new_member_id = body.getRaw(2); + checkLength(new_member_id, MessageId.LENGTH); + + // signature (raw) + // a signature with the member's private key over a list with 5 elements + byte[] signature = body.getRaw(3); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key, new_member_id); + clientHelper.verifySignature(signature, member_public_key, signed); + + // The new member message is a dependency + Collection dependencies = + Collections.singleton(new MessageId(new_member_id)); + + // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - Collection dependencies = Collections.emptyList(); return new BdfMessageContext(meta, dependencies); } + private BdfMessageContext validatePost(Message m, Group g, BdfList body, + String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with six elements + checkSize(body, 6); + + // parent_id (raw or null) + // the identifier of the post to which this is a reply, if any + byte[] parent_id = body.getOptionalRaw(2); + if (parent_id != null) { + checkLength(parent_id, MessageId.LENGTH); + } + + // previous_message_id (raw) + // the identifier of the member's previous post or join message + byte[] previous_message_id = body.getRaw(3); + checkLength(previous_message_id, MessageId.LENGTH); + + // content (string) + String content = body.getString(4); + checkLength(content, 0, MAX_GROUP_POST_BODY_LENGTH); + + // signature (raw) + // a signature with the member's private key over a list with 7 elements + byte[] signature = body.getRaw(5); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key, parent_id, previous_message_id, content); + clientHelper.verifySignature(signature, member_public_key, signed); + + // The parent post, if any, + // and the member's previous message are dependencies + Collection dependencies = new ArrayList(); + if (parent_id != null) dependencies.add(new MessageId(parent_id)); + dependencies.add(new MessageId(previous_message_id)); + + // Return the metadata and dependencies + BdfDictionary meta = new BdfDictionary(); + return new BdfMessageContext(meta, dependencies); + } + + private void addMessageMetadata(BdfMessageContext c, String authorName, + byte[] pubKey, long time) { + c.getDictionary().put(KEY_TIMESTAMP, time); + c.getDictionary().put(KEY_READ, false); + c.getDictionary().put(KEY_AUTHOR_NAME, authorName); + c.getDictionary().put(KEY_AUTHOR_PUBLIC_KEY, pubKey); + } + } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 05c2d4021..ee861267e 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -198,6 +198,12 @@ 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. + // TODO JOIN new_member_id must be the identifier of a NEW_MEMBER message with the same member_name and member_public_key + return true; } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java index 570f697a6..55fa0894a 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java @@ -2,9 +2,7 @@ package org.briarproject.privategroup; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.ContactManager; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; -import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.messaging.ConversationManager; import org.briarproject.api.privategroup.GroupMessageFactory; @@ -59,11 +57,11 @@ public class PrivateGroupModule { @Provides @Singleton GroupMessageValidator provideGroupMessageValidator( - ValidationManager validationManager, CryptoComponent crypto, - AuthorFactory authorFactory, ClientHelper clientHelper, + PrivateGroupFactory groupFactory, + ValidationManager validationManager, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { - GroupMessageValidator validator = new GroupMessageValidator(crypto, - authorFactory, clientHelper, metadataEncoder, clock); + GroupMessageValidator validator = new GroupMessageValidator( + groupFactory, clientHelper, metadataEncoder, clock); validationManager.registerMessageValidator( PrivateGroupManagerImpl.CLIENT_ID, validator); return validator; From e06726b2f9ccbb2c82a897e9f66f4a489003ce89 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 19 Oct 2016 18:28:35 -0200 Subject: [PATCH 03/11] Implement New Member and Join Announcements in GroupMessageFactory --- .../briarproject/api/clients/BaseMessage.java | 1 - .../api/privategroup/GroupMessage.java | 11 ++- .../api/privategroup/GroupMessageFactory.java | 54 ++++++++++-- .../privategroup/GroupMessageFactoryImpl.java | 87 ++++++++++++++++--- 4 files changed, 127 insertions(+), 26 deletions(-) diff --git a/briar-api/src/org/briarproject/api/clients/BaseMessage.java b/briar-api/src/org/briarproject/api/clients/BaseMessage.java index fbfb56337..ee3c0319f 100644 --- a/briar-api/src/org/briarproject/api/clients/BaseMessage.java +++ b/briar-api/src/org/briarproject/api/clients/BaseMessage.java @@ -3,7 +3,6 @@ package org.briarproject.api.clients; import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.annotation.concurrent.Immutable; diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java index 06f277460..4de6bbbb0 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java @@ -5,7 +5,6 @@ import org.briarproject.api.identity.Author; import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.annotation.concurrent.Immutable; @@ -14,16 +13,16 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class GroupMessage extends BaseMessage { - private final Author author; + private final Author member; public GroupMessage(Message message, @Nullable MessageId parent, - Author author) { + Author member) { super(message, parent); - this.author = author; + this.member = member; } - public Author getAuthor() { - return author; + public Author getMember() { + return member; } } diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java index 76f46314e..26e7ae9c7 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java @@ -1,20 +1,58 @@ package org.briarproject.api.privategroup; -import org.briarproject.api.FormatException; -import org.briarproject.api.crypto.PrivateKey; +import org.briarproject.api.crypto.CryptoExecutor; 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; +import org.jetbrains.annotations.Nullable; public interface GroupMessageFactory { - @NotNull + /** + * Creates a new member announcement that contains the joiner's identity + * and is signed by the creator. + *

+ * When a new member accepts an invitation to the group, + * the creator sends this new member announcement to the group. + * + * @param groupId The ID of the group the new member joined + * @param timestamp The current timestamp + * @param creator The creator of the group with {@param groupId} + * @param member The new member that has just accepted an invitation + */ + @CryptoExecutor + GroupMessage createNewMemberMessage(GroupId groupId, long timestamp, + LocalAuthor creator, Author member); + + /** + * Creates a join announcement message + * that depends on a previous new member announcement. + * + * @param groupId The ID of the Group that is being joined + * @param timestamp Must be equal to the timestamp of the new member message + * @param member Our own LocalAuthor + * @param newMemberId The MessageId of the new member message + */ + @CryptoExecutor + GroupMessage createJoinMessage(GroupId groupId, long timestamp, + LocalAuthor member, MessageId newMemberId); + + /** + * Creates a group message + * + * @param groupId The ID of the Group that is posted in + * @param timestamp Must be greater than the timestamps of the parentId + * post, if any, and the member's previous message + * @param parentId The ID of the message that is replied to + * @param author The author of the group message + * @param body The content of the group message + * @param previousMsgId The ID of the author's previous message + * in this group + */ + @CryptoExecutor GroupMessage createGroupMessage(GroupId groupId, long timestamp, - MessageId parent, LocalAuthor author, String body) - throws FormatException, GeneralSecurityException; + @Nullable MessageId parentId, LocalAuthor author, String body, + MessageId previousMsgId); } diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java index 271f7712c..4569bc50d 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java @@ -3,18 +3,25 @@ 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.identity.LocalAuthor; +import org.briarproject.api.nullsafety.NotNullByDefault; 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 org.jetbrains.annotations.Nullable; import java.security.GeneralSecurityException; import javax.inject.Inject; +import static org.briarproject.api.privategroup.MessageType.JOIN; +import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; +import static org.briarproject.api.privategroup.MessageType.POST; + +@NotNullByDefault class GroupMessageFactoryImpl implements GroupMessageFactory { private final ClientHelper clientHelper; @@ -24,20 +31,78 @@ class GroupMessageFactoryImpl implements GroupMessageFactory { this.clientHelper = clientHelper; } - @NotNull + @Override + public GroupMessage createNewMemberMessage(GroupId groupId, long timestamp, + LocalAuthor creator, Author member) { + try { + // Generate the signature + BdfList toSign = BdfList.of(groupId, timestamp, member.getName(), + member.getPublicKey()); + byte[] signature = + clientHelper.sign(toSign, creator.getPrivateKey()); + + // Compose the message + BdfList body = + BdfList.of(NEW_MEMBER.getInt(), member.getName(), + member.getPublicKey(), signature); + Message m = clientHelper.createMessage(groupId, timestamp, body); + + return new GroupMessage(m, null, member); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + + @Override + public GroupMessage createJoinMessage(GroupId groupId, long timestamp, + LocalAuthor member, MessageId newMemberId) { + try { + // Generate the signature + BdfList toSign = BdfList.of(groupId, timestamp, member.getName(), + member.getPublicKey(), newMemberId); + byte[] signature = + clientHelper.sign(toSign, member.getPrivateKey()); + + // Compose the message + BdfList body = + BdfList.of(JOIN.getInt(), member.getName(), + member.getPublicKey(), newMemberId, signature); + Message m = clientHelper.createMessage(groupId, timestamp, body); + + return new GroupMessage(m, null, member); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + @Override public GroupMessage createGroupMessage(GroupId groupId, long timestamp, - MessageId parent, LocalAuthor author, String body) - throws FormatException, GeneralSecurityException { + @Nullable MessageId parentId, LocalAuthor author, String content, + MessageId previousMsgId) { + try { + // Generate the signature + BdfList toSign = BdfList.of(groupId, timestamp, author.getName(), + author.getPublicKey(), parentId, previousMsgId, content); + byte[] signature = + clientHelper.sign(toSign, author.getPrivateKey()); - // Generate the signature - byte[] sig = clientHelper.sign(new BdfList(), author.getPrivateKey()); + // Compose the message + BdfList body = + BdfList.of(POST.getInt(), author.getName(), + author.getPublicKey(), parentId, previousMsgId, + content, signature); + Message m = clientHelper.createMessage(groupId, timestamp, body); - // Compose the message - Message m = - clientHelper.createMessage(groupId, timestamp, new BdfList()); - - return new GroupMessage(m, parent, author); + return new GroupMessage(m, parentId, author); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (FormatException e) { + throw new RuntimeException(e); + } } } From 4f4f1956eb8dc57f45e53b3000ddac0d7239e7b6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 20 Oct 2016 12:45:56 -0200 Subject: [PATCH 04/11] Creator automatically joins the group after creating it --- .../android/AndroidComponent.java | 6 + .../conversation/GroupControllerImpl.java | 12 +- .../creation/CreateGroupControllerImpl.java | 70 ++++++++++- .../threaded/ThreadListControllerImpl.java | 1 + .../api/privategroup/PrivateGroupManager.java | 28 ++--- .../briarproject/privategroup/Constants.java | 2 + .../privategroup/PrivateGroupManagerImpl.java | 110 +++++++++++++----- 7 files changed, 178 insertions(+), 51 deletions(-) diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index 6081f1da9..a436f9430 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 8a6644efd..24b2c6fb2 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 a35c2ac20..db661c43d 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 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 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 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 80a5b44ea..4f348280e 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 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 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 b392abb8f..0f28ce927 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 ee861267e..78c2693f6 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 { - return groupMessageFactory - .createGroupMessage(groupId, timestamp, parentId, author, - body); + previousMsgId = getPreviousMsgId(txn, g); + txn.setComplete(); } catch (FormatException e) { - throw new RuntimeException(e); - } catch (GeneralSecurityException e) { - throw new RuntimeException(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 { + BdfDictionary d = clientHelper.getMessageMetadataAsDictionary(id); + return d.getLong(KEY_TIMESTAMP); + } catch (FormatException 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()); + } + } From 2c8aaa215c1f460a7d7f8489d19073a34c702798 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 20 Oct 2016 16:34:11 -0200 Subject: [PATCH 05/11] Posting group messages takes previous message into account --- .../android/forum/ForumControllerImpl.java | 54 ++++++++++++---- .../conversation/GroupControllerImpl.java | 63 ++++++++++++++---- .../android/threaded/ThreadListActivity.java | 3 +- .../threaded/ThreadListController.java | 3 +- .../threaded/ThreadListControllerImpl.java | 64 ++----------------- 5 files changed, 100 insertions(+), 87 deletions(-) diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java index a0b89b0ed..5da9ee98c 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java @@ -3,6 +3,7 @@ package org.briarproject.android.forum; import android.support.annotation.Nullable; import org.briarproject.android.api.AndroidNotificationManager; +import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.threaded.ThreadListControllerImpl; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.crypto.CryptoExecutor; @@ -28,8 +29,11 @@ import java.util.logging.Logger; import javax.inject.Inject; -public class ForumControllerImpl extends - ThreadListControllerImpl +import static java.lang.Math.max; +import static java.util.logging.Level.WARNING; + +public class ForumControllerImpl + extends ThreadListControllerImpl implements ForumController { private static final Logger LOG = @@ -42,9 +46,9 @@ public class ForumControllerImpl extends LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, ForumManager forumManager, EventBus eventBus, - AndroidNotificationManager notificationManager, Clock clock) { + Clock clock, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, notificationManager, clock); + eventBus, clock, notificationManager); this.forumManager = forumManager; } @@ -94,16 +98,42 @@ public class ForumControllerImpl extends } @Override - protected long getLatestTimestamp() throws DbException { - GroupCount count = forumManager.getGroupCount(getGroupId()); - return count.getLatestMsgTime(); + public void createAndStoreMessage(final String body, + @Nullable final ForumItem parentItem, + final ResultExceptionHandler handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + LocalAuthor author = identityManager.getLocalAuthor(); + GroupCount count = forumManager.getGroupCount(getGroupId()); + long timestamp = max(count.getLatestMsgTime() + 1, + clock.currentTimeMillis()); + MessageId parentId = parentItem != null ? + parentItem.getId() : null; + createMessage(body, timestamp, parentId, author, handler); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); } - @Override - protected ForumPost createLocalMessage(String body, long timestamp, - @Nullable MessageId parentId, LocalAuthor author) { - return forumManager.createLocalPost(getGroupId(), body, timestamp, - parentId, author); + private void createMessage(final String body, final long timestamp, + final @Nullable MessageId parentId, final LocalAuthor author, + final ResultExceptionHandler handler) { + cryptoExecutor.execute(new Runnable() { + @Override + public void run() { + LOG.info("Creating forum post..."); + ForumPost msg = forumManager + .createLocalPost(getGroupId(), body, timestamp, + parentId, author); + storePost(msg, body, handler); + } + }); } @Override 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 24b2c6fb2..6b34d19cd 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -3,8 +3,8 @@ package org.briarproject.android.privategroup.conversation; import android.support.annotation.Nullable; import org.briarproject.android.api.AndroidNotificationManager; +import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.threaded.ThreadListControllerImpl; -import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; @@ -28,6 +28,9 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static java.lang.Math.max; +import static java.util.logging.Level.WARNING; + public class GroupControllerImpl extends ThreadListControllerImpl implements GroupController { @@ -44,9 +47,9 @@ public class GroupControllerImpl extends @CryptoExecutor Executor cryptoExecutor, PrivateGroupManager privateGroupManager, GroupMessageFactory groupMessageFactory, EventBus eventBus, - AndroidNotificationManager notificationManager, Clock clock) { + Clock clock, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, notificationManager, clock); + eventBus, clock, notificationManager); this.privateGroupManager = privateGroupManager; this.groupMessageFactory = groupMessageFactory; } @@ -97,18 +100,52 @@ public class GroupControllerImpl extends } @Override - protected long getLatestTimestamp() throws DbException { - GroupCount count = privateGroupManager.getGroupCount(getGroupId()); - return count.getLatestMsgTime(); + public void createAndStoreMessage(final String body, + @Nullable final GroupMessageItem parentItem, + final ResultExceptionHandler handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + LocalAuthor author = identityManager.getLocalAuthor(); + MessageId parentId = null; + MessageId previousMsgId = + privateGroupManager.getPreviousMsgId(getGroupId()); + // timestamp must be greater than the timestamps + // of the member's previous message... + long timestamp = privateGroupManager + .getMessageTimestamp(previousMsgId); + // ...and the parent post, if any + if (parentItem != null) { + timestamp = max(parentItem.getTimestamp(), timestamp); + parentId = parentItem.getId(); + } + timestamp = max(clock.currentTimeMillis(), timestamp + 1); + createMessage(body, timestamp, parentId, author, + previousMsgId, handler); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); } - @Override - protected GroupMessage createLocalMessage(String body, long timestamp, - @Nullable MessageId parentId, LocalAuthor author) { - MessageId previousMsgId = null; // TODO - return groupMessageFactory - .createGroupMessage(getGroupId(), timestamp, parentId, - author, body, previousMsgId); + private void createMessage(final String body, final long timestamp, + final @Nullable MessageId parentId, final LocalAuthor author, + final MessageId previousMsgId, + final ResultExceptionHandler handler) { + cryptoExecutor.execute(new Runnable() { + @Override + public void run() { + LOG.info("Creating group message..."); + GroupMessage msg = groupMessageFactory + .createGroupMessage(getGroupId(), timestamp, + parentId, author, body, previousMsgId); + storePost(msg, body, handler); + } + }); } @Override diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java index a8eefd250..268fdcca9 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java @@ -249,8 +249,7 @@ public abstract class ThreadListActivity items); - void createAndStoreMessage(String body, @Nullable MessageId parentId, + void createAndStoreMessage(String body, @Nullable I parentItem, ResultExceptionHandler handler); void deleteNamedGroup(ResultExceptionHandler handler); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java index 4f348280e..d584d8704 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java @@ -2,7 +2,6 @@ package org.briarproject.android.threaded; import android.app.Activity; import android.support.annotation.CallSuper; -import android.support.annotation.Nullable; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.DbControllerImpl; @@ -18,7 +17,6 @@ import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.GroupRemovedEvent; import org.briarproject.api.identity.IdentityManager; -import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; @@ -43,12 +41,12 @@ public abstract class ThreadListControllerImpl bodyCache = new ConcurrentHashMap<>(); @@ -59,13 +57,13 @@ public abstract class ThreadListControllerImpl handler) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - long now = System.currentTimeMillis(); - LocalAuthor author = identityManager.getLocalAuthor(); - long timestamp = getLatestTimestamp(); - timestamp = Math.max(timestamp, clock.currentTimeMillis()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) { - LOG.info("Loading identity and timestamp took " + - duration + " ms"); - } - createMessage(body, timestamp, parentId, author, handler); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - handler.onException(e); - } - } - }); - } - - @DatabaseExecutor - protected abstract long getLatestTimestamp() throws DbException; - - private void createMessage(final String body, final long timestamp, - final @Nullable MessageId parentId, final LocalAuthor author, - final ResultExceptionHandler handler) { - cryptoExecutor.execute(new Runnable() { - @Override - public void run() { - long now = System.currentTimeMillis(); - M msg = createLocalMessage(body, timestamp, parentId, author); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Creating message took " + duration + " ms"); - storePost(msg, body, handler); - } - }); - } - - @CryptoExecutor - protected abstract M createLocalMessage(String body, long timestamp, - @Nullable MessageId parentId, LocalAuthor author); - - private void storePost(final M msg, final String body, + protected void storePost(final M msg, final String body, final ResultExceptionHandler resultHandler) { runOnDbThread(new Runnable() { @Override From 349a34ffd87b267866b8e86fd5cc1a8a8d35668d Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 20 Oct 2016 17:54:52 -0200 Subject: [PATCH 06/11] Return actual private group message headers and bodies to the UI --- .../api/privategroup/GroupMessageHeader.java | 10 +- .../api/privategroup/JoinMessageHeader.java | 21 ++++ .../briarproject/privategroup/Constants.java | 2 + .../privategroup/GroupMessageValidator.java | 11 +- .../privategroup/PrivateGroupManagerImpl.java | 115 +++++++++++++++--- .../privategroup/PrivateGroupModule.java | 9 +- 6 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java index 21043297b..9789a59e0 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java @@ -3,19 +3,23 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.PostHeader; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault public class GroupMessageHeader extends PostHeader { private final GroupId groupId; - public GroupMessageHeader(@NotNull GroupId groupId, @NotNull MessageId id, + public GroupMessageHeader(GroupId groupId, MessageId id, @Nullable MessageId parentId, long timestamp, - @NotNull Author author, @NotNull Status authorStatus, - boolean read) { + Author author, Status authorStatus, boolean read) { super(id, parentId, timestamp, author, authorStatus, read); this.groupId = groupId; } diff --git a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java new file mode 100644 index 000000000..ef8c3b337 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java @@ -0,0 +1,21 @@ +package org.briarproject.api.privategroup; + +import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class JoinMessageHeader extends GroupMessageHeader { + + public JoinMessageHeader(GroupId groupId, MessageId id, + @Nullable MessageId parentId, long timestamp, Author author, + Author.Status authorStatus, boolean read) { + super(groupId, id, parentId, timestamp, author, authorStatus, read); + } + +} diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index 0f28ce927..b67d715d4 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -8,6 +8,8 @@ interface Constants { String KEY_TYPE = "type"; String KEY_TIMESTAMP = "timestamp"; String KEY_READ = MSG_KEY_READ; + String KEY_PARENT_ID = "parentId"; + String KEY_AUTHOR_ID = "authorId"; String KEY_AUTHOR_NAME = "authorName"; String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey"; diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 1bd94100b..1fcaec82d 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -6,6 +6,8 @@ import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.privategroup.MessageType; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; @@ -24,8 +26,10 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENG import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID; 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_PARENT_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; @@ -33,12 +37,14 @@ import static org.briarproject.privategroup.Constants.KEY_TYPE; class GroupMessageValidator extends BdfMessageValidator { private final PrivateGroupFactory groupFactory; + private final AuthorFactory authorFactory; GroupMessageValidator(PrivateGroupFactory groupFactory, ClientHelper clientHelper, MetadataEncoder metadataEncoder, - Clock clock) { + Clock clock, AuthorFactory authorFactory) { super(clientHelper, metadataEncoder, clock); this.groupFactory = groupFactory; + this.authorFactory = authorFactory; } @Override @@ -179,6 +185,7 @@ class GroupMessageValidator extends BdfMessageValidator { // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); + if (parent_id != null) meta.put(KEY_PARENT_ID, parent_id); return new BdfMessageContext(meta, dependencies); } @@ -186,6 +193,8 @@ class GroupMessageValidator extends BdfMessageValidator { byte[] pubKey, long time) { c.getDictionary().put(KEY_TIMESTAMP, time); c.getDictionary().put(KEY_READ, false); + Author a = authorFactory.createAuthor(authorName, pubKey); + c.getDictionary().put(KEY_AUTHOR_ID, a.getId()); c.getDictionary().put(KEY_AUTHOR_NAME, authorName); c.getDictionary().put(KEY_AUTHOR_PUBLIC_KEY, pubKey); } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 78c2693f6..891091353 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -9,9 +9,13 @@ 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.Author.Status; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessageHeader; -import org.briarproject.api.privategroup.MessageType; +import org.briarproject.api.privategroup.JoinMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; @@ -26,14 +30,23 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Logger; import javax.inject.Inject; import static org.briarproject.api.identity.Author.Status.OURSELVES; +import static org.briarproject.api.privategroup.MessageType.JOIN; +import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; +import static org.briarproject.api.privategroup.MessageType.POST; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID; 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_PARENT_ID; 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; @@ -49,17 +62,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements + "67657220627920546f727374656e2047")); private final PrivateGroupFactory privateGroupFactory; + private final IdentityManager identityManager; @Inject PrivateGroupManagerImpl(ClientHelper clientHelper, MetadataParser metadataParser, DatabaseComponent db, - PrivateGroupFactory privateGroupFactory) { + PrivateGroupFactory privateGroupFactory, + IdentityManager identityManager) { super(db, clientHelper, metadataParser); this.privateGroupFactory = privateGroupFactory; + this.identityManager = identityManager; } - @NotNull @Override public ClientId getClientId() { return CLIENT_ID; @@ -85,14 +100,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements private void announceNewMember(Transaction txn, GroupMessage m) throws DbException, FormatException { BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_TYPE, MessageType.NEW_MEMBER.getInt()); + meta.put(KEY_TYPE, 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()); + meta.put(KEY_TYPE, JOIN.getInt()); addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); trackOutgoingMessage(txn, m.getMessage()); @@ -135,6 +150,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements clientHelper.mergeGroupMetadata(txn, g, d); } + @Override public long getMessageTimestamp(MessageId id) throws DbException { try { BdfDictionary d = clientHelper.getMessageMetadataAsDictionary(id); @@ -150,7 +166,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements Transaction txn = db.startTransaction(false); try { BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_TYPE, MessageType.POST.getInt()); + meta.put(KEY_TYPE, POST.getInt()); + if (m.getParent() != null) meta.put(KEY_PARENT_ID, m.getParent()); addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); trackOutgoingMessage(txn, m.getMessage()); @@ -165,7 +182,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true); } - @NotNull @Override public PrivateGroup getPrivateGroup(GroupId g) throws DbException { PrivateGroup privateGroup; @@ -179,7 +195,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return privateGroup; } - @NotNull @Override public PrivateGroup getPrivateGroup(Transaction txn, GroupId g) throws DbException { @@ -191,7 +206,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } } - @NotNull @Override public Collection getPrivateGroups() throws DbException { Collection groups; @@ -219,18 +233,90 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return false; } - @NotNull @Override public String getMessageBody(MessageId m) throws DbException { - return "empty"; + try { + // TODO remove + if (clientHelper.getMessageMetadataAsDictionary(m).getLong(KEY_TYPE) != POST.getInt()) + return "new member joined"; + + // type(0), member_name(1), member_public_key(2), parent_id(3), + // previous_message_id(4), content(5), signature(6) + return clientHelper.getMessageAsList(m).getString(5); + } catch (FormatException e) { + throw new DbException(e); + } } - @NotNull @Override public Collection getHeaders(GroupId g) throws DbException { + Collection headers = + new ArrayList(); + Transaction txn = db.startTransaction(true); + try { + Map metadata = + clientHelper.getMessageMetadataAsDictionary(txn, g); + // get all authors we need to get the status for + Set authors = new HashSet(); + for (Entry entry : metadata.entrySet()) { + BdfDictionary meta = entry.getValue(); + if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt()) + continue; + byte[] idBytes = meta.getRaw(KEY_AUTHOR_ID); + authors.add(new AuthorId(idBytes)); + } + // get statuses for all authors + Map statuses = new HashMap(); + for (AuthorId id : authors) { + statuses.put(id, identityManager.getAuthorStatus(txn, id)); + } + // Parse the metadata + for (Entry entry : metadata.entrySet()) { + BdfDictionary meta = entry.getValue(); + if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt()) + continue; + headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta, + statuses)); + } + txn.setComplete(); + return headers; + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } - return Collections.emptyList(); + private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g, + MessageId id, BdfDictionary meta, Map statuses) + throws DbException, FormatException { + + MessageId parentId = null; + if (meta.containsKey(KEY_PARENT_ID)) { + parentId = new MessageId(meta.getRaw(KEY_PARENT_ID)); + } + long timestamp = meta.getLong(KEY_TIMESTAMP); + + AuthorId authorId = new AuthorId(meta.getRaw(KEY_AUTHOR_ID)); + String name = meta.getString(KEY_AUTHOR_NAME); + byte[] publicKey = meta.getRaw(KEY_AUTHOR_PUBLIC_KEY); + Author author = new Author(authorId, name, publicKey); + + Status status; + if (statuses.containsKey(authorId)) { + status = statuses.get(authorId); + } else { + status = identityManager.getAuthorStatus(txn, author.getId()); + } + boolean read = meta.getBoolean(KEY_READ); + + if (meta.getLong(KEY_TYPE) == JOIN.getInt()) { + return new JoinMessageHeader(g, id, parentId, timestamp, author, + status, read); + } + return new GroupMessageHeader(g, id, parentId, timestamp, author, + status, read); } @Override @@ -251,6 +337,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements boolean read) { meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp()); meta.put(KEY_READ, read); + meta.put(KEY_AUTHOR_ID, m.getMember().getId()); meta.put(KEY_AUTHOR_NAME, m.getMember().getName()); meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey()); } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java index 55fa0894a..49c0714c5 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java @@ -3,6 +3,7 @@ package org.briarproject.privategroup; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.messaging.ConversationManager; import org.briarproject.api.privategroup.GroupMessageFactory; @@ -59,11 +60,15 @@ public class PrivateGroupModule { GroupMessageValidator provideGroupMessageValidator( PrivateGroupFactory groupFactory, ValidationManager validationManager, ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { + MetadataEncoder metadataEncoder, Clock clock, + AuthorFactory authorFactory) { + GroupMessageValidator validator = new GroupMessageValidator( - groupFactory, clientHelper, metadataEncoder, clock); + groupFactory, clientHelper, metadataEncoder, clock, + authorFactory); validationManager.registerMessageValidator( PrivateGroupManagerImpl.CLIENT_ID, validator); + return validator; } From 679b54b2b442b7c22270ea21593229e69616a4dc Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 21 Oct 2016 12:36:40 -0200 Subject: [PATCH 07/11] Show join messages properly in the threaded conversation --- ...em_forum_post.xml => list_item_thread.xml} | 5 +- .../res/layout/list_item_thread_notice.xml | 48 +++++++ briar-android/res/values/strings.xml | 1 + .../briarproject/android/ActivityModule.java | 1 + .../android/forum/ForumActivity.java | 9 +- .../android/forum/ForumControllerImpl.java | 4 +- .../android/forum/NestedForumAdapter.java | 28 ---- .../android/forum/NestedForumHolder.java | 13 -- .../conversation/GroupControllerImpl.java | 14 +- .../conversation/GroupMessageAdapter.java | 22 +++- .../conversation/GroupMessageItem.java | 2 +- .../conversation/GroupMessageViewHolder.java | 14 -- .../conversation/JoinMessageItem.java | 22 ++++ .../threaded/BaseThreadItemViewHolder.java | 122 ++++++++++++++++++ .../android/threaded/ThreadItem.java | 7 +- .../android/threaded/ThreadItemAdapter.java | 18 ++- .../threaded/ThreadItemViewHolder.java | 100 +------------- .../threaded/ThreadListController.java | 3 + .../threaded/ThreadListControllerImpl.java | 8 +- .../android/forum/ForumActivityTest.java | 3 +- .../android/forum/TestForumActivity.java | 3 +- .../privategroup/PrivateGroupManagerImpl.java | 5 - 22 files changed, 274 insertions(+), 178 deletions(-) rename briar-android/res/layout/{list_item_forum_post.xml => list_item_thread.xml} (98%) create mode 100644 briar-android/res/layout/list_item_thread_notice.xml delete mode 100644 briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java delete mode 100644 briar-android/src/org/briarproject/android/forum/NestedForumHolder.java delete mode 100644 briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java create mode 100644 briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java diff --git a/briar-android/res/layout/list_item_forum_post.xml b/briar-android/res/layout/list_item_thread.xml similarity index 98% rename from briar-android/res/layout/list_item_forum_post.xml rename to briar-android/res/layout/list_item_thread.xml index 2fe006721..f788c0ce8 100644 --- a/briar-android/res/layout/list_item_forum_post.xml +++ b/briar-android/res/layout/list_item_thread.xml @@ -1,12 +1,13 @@ + android:orientation="horizontal" + android:baselineAligned="false"> + + + + + + + + + + + + + diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 878b281e4..132cea768 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -166,6 +166,7 @@ Invite Members Leave Group Dissolve Group + joined the group. Group Invitations diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index 174a5c946..6550d1752 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -120,6 +120,7 @@ public class ActivityModule { @Provides protected GroupController provideGroupController( GroupControllerImpl groupController) { + activity.addLifecycleController(groupController); return groupController; } diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 307a7c5e1..31816fa8f 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -18,8 +18,9 @@ import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; -import org.briarproject.android.sharing.ShareForumActivity; import org.briarproject.android.sharing.ForumSharingStatusActivity; +import org.briarproject.android.sharing.ShareForumActivity; +import org.briarproject.android.threaded.ThreadItemAdapter; import org.briarproject.android.threaded.ThreadListActivity; import org.briarproject.android.threaded.ThreadListController; import org.briarproject.api.db.DbException; @@ -35,7 +36,7 @@ import static android.widget.Toast.LENGTH_SHORT; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; public class ForumActivity extends - ThreadListActivity { + ThreadListActivity> { private static final int REQUEST_FORUM_SHARED = 3; @@ -74,9 +75,9 @@ public class ForumActivity extends } @Override - protected NestedForumAdapter createAdapter( + protected ThreadItemAdapter createAdapter( LinearLayoutManager layoutManager) { - return new NestedForumAdapter(this, layoutManager); + return new ThreadItemAdapter<>(this, layoutManager); } @Override diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java index 5da9ee98c..25d832846 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java @@ -88,8 +88,8 @@ public class ForumControllerImpl } @Override - protected String loadMessageBody(MessageId id) throws DbException { - return StringUtils.fromUtf8(forumManager.getPostBody(id)); + protected String loadMessageBody(ForumPostHeader h) throws DbException { + return StringUtils.fromUtf8(forumManager.getPostBody(h.getId())); } @Override diff --git a/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java b/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java deleted file mode 100644 index 08d68b961..000000000 --- a/briar-android/src/org/briarproject/android/forum/NestedForumAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.briarproject.android.forum; - -import android.support.annotation.UiThread; -import android.support.v7.widget.LinearLayoutManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.briarproject.R; -import org.briarproject.android.threaded.ThreadItemAdapter; - -@UiThread -class NestedForumAdapter extends ThreadItemAdapter { - - NestedForumAdapter(ThreadItemListener listener, - LinearLayoutManager layoutManager) { - super(listener, layoutManager); - } - - @Override - public NestedForumHolder onCreateViewHolder(ViewGroup parent, - int viewType) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.list_item_forum_post, parent, false); - return new NestedForumHolder(v); - } - -} diff --git a/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java b/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java deleted file mode 100644 index b73558ff5..000000000 --- a/briar-android/src/org/briarproject/android/forum/NestedForumHolder.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.briarproject.android.forum; - -import android.view.View; - -import org.briarproject.android.threaded.ThreadItemViewHolder; - -public class NestedForumHolder extends ThreadItemViewHolder { - - public NestedForumHolder(View v) { - super(v); - } - -} 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 6b34d19cd..8d61bbff8 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -2,6 +2,7 @@ package org.briarproject.android.privategroup.conversation; import android.support.annotation.Nullable; +import org.briarproject.R; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.threaded.ThreadListControllerImpl; @@ -17,6 +18,7 @@ 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.JoinMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.sync.MessageId; @@ -90,8 +92,13 @@ public class GroupControllerImpl extends } @Override - protected String loadMessageBody(MessageId id) throws DbException { - return privateGroupManager.getMessageBody(id); + protected String loadMessageBody(GroupMessageHeader header) + throws DbException { + if (header instanceof JoinMessageHeader) { + return listener.getApplicationContext() + .getString(R.string.groups_member_joined); + } + return privateGroupManager.getMessageBody(header.getId()); } @Override @@ -162,6 +169,9 @@ public class GroupControllerImpl extends @Override protected GroupMessageItem buildItem(GroupMessageHeader header, String body) { + if (header instanceof JoinMessageHeader) { + return new JoinMessageItem(header, body); + } return new GroupMessageItem(header, body); } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java index 19ee14adc..2a9c75a00 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java @@ -7,7 +7,9 @@ import android.view.View; import android.view.ViewGroup; import org.briarproject.R; +import org.briarproject.android.threaded.BaseThreadItemViewHolder; import org.briarproject.android.threaded.ThreadItemAdapter; +import org.briarproject.android.threaded.ThreadItemViewHolder; @UiThread public class GroupMessageAdapter extends ThreadItemAdapter { @@ -18,11 +20,23 @@ public class GroupMessageAdapter extends ThreadItemAdapter { } @Override - public GroupMessageViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) { + public int getItemViewType(int position) { + GroupMessageItem item = getVisibleItem(position); + if (item instanceof JoinMessageItem) { + return R.layout.list_item_thread_notice; + } + return R.layout.list_item_thread; + } + + @Override + public BaseThreadItemViewHolder onCreateViewHolder( + ViewGroup parent, int type) { View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.list_item_forum_post, parent, false); - return new GroupMessageViewHolder(v); + .inflate(type, parent, false); + if (type == R.layout.list_item_thread_notice) { + return new BaseThreadItemViewHolder<>(v); + } + return new ThreadItemViewHolder<>(v); } } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java index 7bde4a8bb..b961fd3f6 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java @@ -8,7 +8,7 @@ import org.briarproject.api.sync.MessageId; class GroupMessageItem extends ThreadItem { - GroupMessageItem(MessageId messageId, MessageId parentId, + private GroupMessageItem(MessageId messageId, MessageId parentId, String text, long timestamp, Author author, Status status, boolean isRead) { super(messageId, parentId, text, timestamp, author, status, isRead); diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java deleted file mode 100644 index 11825b805..000000000 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.briarproject.android.privategroup.conversation; - -import android.view.View; - -import org.briarproject.android.threaded.ThreadItemViewHolder; - -public class GroupMessageViewHolder - extends ThreadItemViewHolder { - - public GroupMessageViewHolder(View v) { - super(v); - } - -} diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java new file mode 100644 index 000000000..e21399127 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java @@ -0,0 +1,22 @@ +package org.briarproject.android.privategroup.conversation; + +import org.briarproject.api.privategroup.GroupMessageHeader; + +class JoinMessageItem extends GroupMessageItem { + + JoinMessageItem(GroupMessageHeader h, + String text) { + super(h, text); + } + + @Override + public int getLevel() { + return 0; + } + + @Override + public boolean hasDescendants() { + return false; + } + +} diff --git a/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java b/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java new file mode 100644 index 000000000..47ed3502f --- /dev/null +++ b/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java @@ -0,0 +1,122 @@ +package org.briarproject.android.threaded; + +import android.animation.Animator; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.support.annotation.CallSuper; +import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener; +import org.briarproject.android.view.AuthorView; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.util.StringUtils; + +@UiThread +@NotNullByDefault +public class BaseThreadItemViewHolder + extends RecyclerView.ViewHolder { + + private final static int ANIMATION_DURATION = 5000; + + private final ViewGroup layout; + private final TextView textView; + private final AuthorView author; + private final View topDivider; + + public BaseThreadItemViewHolder(View v) { + super(v); + + layout = (ViewGroup) v.findViewById(R.id.layout); + textView = (TextView) v.findViewById(R.id.text); + author = (AuthorView) v.findViewById(R.id.author); + topDivider = v.findViewById(R.id.top_divider); + } + + // TODO improve encapsulation, so we don't need to pass the adapter here + @CallSuper + public void bind(final ThreadItemAdapter adapter, + final ThreadItemListener listener, final I item, int pos) { + + textView.setText(StringUtils.trim(item.getText())); + + if (pos == 0) { + topDivider.setVisibility(View.INVISIBLE); + } else { + topDivider.setVisibility(View.VISIBLE); + } + + author.setAuthor(item.getAuthor()); + author.setDate(item.getTimestamp()); + author.setAuthorStatus(item.getStatus()); + + if (item.equals(adapter.getReplyItem())) { + layout.setBackgroundColor(ContextCompat + .getColor(getContext(), R.color.forum_cell_highlight)); + } else if (item.equals(adapter.getAddedItem())) { + layout.setBackgroundColor(ContextCompat + .getColor(getContext(), R.color.forum_cell_highlight)); + animateFadeOut(adapter, adapter.getAddedItem()); + adapter.clearAddedItem(); + } else { + layout.setBackgroundColor(ContextCompat + .getColor(getContext(), R.color.window_background)); + } + } + + private void animateFadeOut(final ThreadItemAdapter adapter, + final I addedItem) { + + setIsRecyclable(false); + ValueAnimator anim = new ValueAnimator(); + adapter.addAnimatingItem(addedItem, anim); + ColorDrawable viewColor = (ColorDrawable) layout.getBackground(); + anim.setIntValues(viewColor.getColor(), ContextCompat + .getColor(getContext(), R.color.window_background)); + anim.setEvaluator(new ArgbEvaluator()); + anim.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + setIsRecyclable(true); + adapter.removeAnimatingItem(addedItem); + } + + @Override + public void onAnimationCancel(Animator animation) { + setIsRecyclable(true); + adapter.removeAnimatingItem(addedItem); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + layout.setBackgroundColor( + (Integer) valueAnimator.getAnimatedValue()); + } + }); + anim.setDuration(ANIMATION_DURATION); + anim.start(); + } + + protected Context getContext() { + return textView.getContext(); + } + +} diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItem.java b/briar-android/src/org/briarproject/android/threaded/ThreadItem.java index a5a220759..c4281e9ac 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItem.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItem.java @@ -1,13 +1,18 @@ package org.briarproject.android.threaded; +import android.support.annotation.UiThread; + import org.briarproject.api.clients.MessageTree.MessageNode; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; import org.briarproject.api.sync.MessageId; +import javax.annotation.concurrent.NotThreadSafe; + import static org.briarproject.android.threaded.ThreadItemAdapter.UNDEFINED; -/* This class is not thread safe */ +@UiThread +@NotThreadSafe public abstract class ThreadItem implements MessageNode { private final MessageId messageId; diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index c8ab7f2ba..9e93f8f07 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -5,7 +5,11 @@ import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import org.briarproject.R; import org.briarproject.android.util.VersionedAdapter; import org.briarproject.api.sync.MessageId; @@ -17,8 +21,8 @@ import java.util.Map; import static android.support.v7.widget.RecyclerView.NO_POSITION; -public abstract class ThreadItemAdapter - extends RecyclerView.Adapter> +public class ThreadItemAdapter + extends RecyclerView.Adapter> implements VersionedAdapter { static final int UNDEFINED = -1; @@ -42,7 +46,15 @@ public abstract class ThreadItemAdapter } @Override - public void onBindViewHolder(ThreadItemViewHolder ui, int position) { + public BaseThreadItemViewHolder onCreateViewHolder( + ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_thread, parent, false); + return new ThreadItemViewHolder<>(v); + } + + @Override + public void onBindViewHolder(BaseThreadItemViewHolder ui, int position) { I item = getVisibleItem(position); if (item == null) return; listener.onItemVisible(item); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java index 0b5a5ddc5..c59eac8a4 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java @@ -1,45 +1,30 @@ package org.briarproject.android.threaded; -import android.animation.Animator; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.drawable.ColorDrawable; import android.support.annotation.UiThread; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; import android.view.View; -import android.view.ViewGroup; import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener; -import org.briarproject.android.view.AuthorView; -import org.briarproject.util.StringUtils; +import org.briarproject.api.nullsafety.NotNullByDefault; import static android.view.View.GONE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @UiThread -public abstract class ThreadItemViewHolder - extends RecyclerView.ViewHolder { +@NotNullByDefault +public class ThreadItemViewHolder + extends BaseThreadItemViewHolder { - private final static int ANIMATION_DURATION = 5000; - - private final TextView textView, lvlText, repliesText; - private final AuthorView author; + private final TextView lvlText, repliesText; private final View[] lvls; private final View chevron, replyButton; - private final ViewGroup cell; - private final View topDivider; public ThreadItemViewHolder(View v) { super(v); - textView = (TextView) v.findViewById(R.id.text); lvlText = (TextView) v.findViewById(R.id.nested_line_text); - author = (AuthorView) v.findViewById(R.id.author); repliesText = (TextView) v.findViewById(R.id.replies); int[] nestedLineIds = { R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3, @@ -51,21 +36,13 @@ public abstract class ThreadItemViewHolder } chevron = v.findViewById(R.id.chevron); replyButton = v.findViewById(R.id.btn_reply); - cell = (ViewGroup) v.findViewById(R.id.forum_cell); - topDivider = v.findViewById(R.id.top_divider); } // TODO improve encapsulation, so we don't need to pass the adapter here + @Override public void bind(final ThreadItemAdapter adapter, final ThreadItemListener listener, final I item, int pos) { - - textView.setText(StringUtils.trim(item.getText())); - - if (pos == 0) { - topDivider.setVisibility(View.INVISIBLE); - } else { - topDivider.setVisibility(View.VISIBLE); - } + super.bind(adapter, listener, item, pos); for (int i = 0; i < lvls.length; i++) { lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE); @@ -76,9 +53,6 @@ public abstract class ThreadItemViewHolder } else { lvlText.setVisibility(GONE); } - author.setAuthor(item.getAuthor()); - author.setDate(item.getTimestamp()); - author.setAuthorStatus(item.getStatus()); int replies = adapter.getReplyCount(item); if (replies == 0) { @@ -110,18 +84,6 @@ public abstract class ThreadItemViewHolder } else { chevron.setVisibility(INVISIBLE); } - if (item.equals(adapter.getReplyItem())) { - cell.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.forum_cell_highlight)); - } else if (item.equals(adapter.getAddedItem())) { - cell.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.forum_cell_highlight)); - animateFadeOut(adapter, adapter.getAddedItem()); - adapter.clearAddedItem(); - } else { - cell.setBackgroundColor(ContextCompat - .getColor(getContext(), R.color.window_background)); - } replyButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -131,52 +93,4 @@ public abstract class ThreadItemViewHolder }); } - private void animateFadeOut(final ThreadItemAdapter adapter, - final I addedItem) { - - setIsRecyclable(false); - ValueAnimator anim = new ValueAnimator(); - adapter.addAnimatingItem(addedItem, anim); - ColorDrawable viewColor = (ColorDrawable) cell.getBackground(); - anim.setIntValues(viewColor.getColor(), ContextCompat - .getColor(getContext(), R.color.window_background)); - anim.setEvaluator(new ArgbEvaluator()); - anim.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - setIsRecyclable(true); - adapter.removeAnimatingItem(addedItem); - } - - @Override - public void onAnimationCancel(Animator animation) { - setIsRecyclable(true); - adapter.removeAnimatingItem(addedItem); - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - cell.setBackgroundColor( - (Integer) valueAnimator.getAnimatedValue()); - } - }); - anim.setDuration(ANIMATION_DURATION); - anim.start(); - } - - private Context getContext() { - return textView.getContext(); - } - } diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java index f2e7570a8..43ce1d5c2 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java @@ -1,5 +1,6 @@ package org.briarproject.android.threaded; +import android.content.Context; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -39,6 +40,8 @@ public interface ThreadListController listener; + protected volatile ThreadListListener listener; protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, IdentityManager identityManager, @@ -159,7 +159,7 @@ public abstract class ThreadListControllerImpl loadHeaders() throws DbException; @DatabaseExecutor - protected abstract String loadMessageBody(MessageId id) throws DbException; + protected abstract String loadMessageBody(H header) throws DbException; @Override public void loadItem(final H header, @@ -193,7 +193,7 @@ public abstract class ThreadListControllerImpl dummyData = getDummyData(); verify(mc, times(1)).loadItems(rc.capture()); rc.getValue().onResult(dummyData); - NestedForumAdapter adapter = forumActivity.getAdapter(); + ThreadItemAdapter adapter = forumActivity.getAdapter(); Assert.assertNotNull(adapter); // Cascade close assertEquals(6, adapter.getItemCount()); diff --git a/briar-android/test/java/org/briarproject/android/forum/TestForumActivity.java b/briar-android/test/java/org/briarproject/android/forum/TestForumActivity.java index 806fee299..a77a88cc1 100644 --- a/briar-android/test/java/org/briarproject/android/forum/TestForumActivity.java +++ b/briar-android/test/java/org/briarproject/android/forum/TestForumActivity.java @@ -3,6 +3,7 @@ package org.briarproject.android.forum; import org.briarproject.android.ActivityModule; import org.briarproject.android.controller.BriarController; import org.briarproject.android.controller.BriarControllerImpl; +import org.briarproject.android.threaded.ThreadItemAdapter; import org.mockito.Mockito; /** @@ -15,7 +16,7 @@ public class TestForumActivity extends ForumActivity { return forumController; } - public NestedForumAdapter getAdapter() { + public ThreadItemAdapter getAdapter() { return adapter; } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 891091353..caf8b81ae 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -26,7 +26,6 @@ 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.ArrayList; import java.util.Collection; @@ -236,10 +235,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public String getMessageBody(MessageId m) throws DbException { try { - // TODO remove - if (clientHelper.getMessageMetadataAsDictionary(m).getLong(KEY_TYPE) != POST.getInt()) - return "new member joined"; - // type(0), member_name(1), member_public_key(2), parent_id(3), // previous_message_id(4), content(5), signature(6) return clientHelper.getMessageAsList(m).getString(5); From 0caabda303eee8f75442408a9a967ce2e92d5d9e Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 24 Oct 2016 10:25:14 -0200 Subject: [PATCH 08/11] Do additional validation on incoming private group messages --- .../briarproject/privategroup/Constants.java | 6 +- .../privategroup/GroupMessageValidator.java | 10 +- .../privategroup/PrivateGroupManagerImpl.java | 120 +++++++++++++++--- 3 files changed, 110 insertions(+), 26 deletions(-) diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index b67d715d4..3c83d2326 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -8,11 +8,11 @@ interface Constants { String KEY_TYPE = "type"; String KEY_TIMESTAMP = "timestamp"; String KEY_READ = MSG_KEY_READ; - String KEY_PARENT_ID = "parentId"; + String KEY_PARENT_MSG_ID = "parentMsgId"; + String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId"; + String KEY_PREVIOUS_MSG_ID = "previousMsgId"; String KEY_AUTHOR_ID = "authorId"; 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/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 1fcaec82d..013857939 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -29,7 +29,9 @@ import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_ import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID; 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_PARENT_ID; +import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID; +import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; +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; @@ -80,6 +82,8 @@ class GroupMessageValidator extends BdfMessageValidator { break; case POST: c = validatePost(m, g, body, member_name, member_public_key); + addMessageMetadata(c, member_name, member_public_key, + m.getTimestamp()); break; default: throw new InvalidMessageException("Unknown Message Type"); @@ -141,6 +145,7 @@ class GroupMessageValidator extends BdfMessageValidator { // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_NEW_MEMBER_MSG_ID, new_member_id); return new BdfMessageContext(meta, dependencies); } @@ -185,7 +190,8 @@ class GroupMessageValidator extends BdfMessageValidator { // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - if (parent_id != null) meta.put(KEY_PARENT_ID, parent_id); + if (parent_id != null) meta.put(KEY_PARENT_MSG_ID, parent_id); + meta.put(KEY_PREVIOUS_MSG_ID, previous_message_id); return new BdfMessageContext(meta, dependencies); } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index caf8b81ae..eb3a888f0 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -16,6 +16,7 @@ import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.JoinMessageHeader; +import org.briarproject.api.privategroup.MessageType; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; @@ -28,6 +29,7 @@ import org.briarproject.clients.BdfIncomingMessageHook; import org.briarproject.util.StringUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -45,7 +47,8 @@ import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID; 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_PARENT_ID; +import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID; +import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; 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; @@ -100,6 +103,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements throws DbException, FormatException { BdfDictionary meta = new BdfDictionary(); meta.put(KEY_TYPE, NEW_MEMBER.getInt()); + addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); } @@ -166,9 +170,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements try { BdfDictionary meta = new BdfDictionary(); meta.put(KEY_TYPE, POST.getInt()); - if (m.getParent() != null) meta.put(KEY_PARENT_ID, m.getParent()); + if (m.getParent() != null) meta.put(KEY_PARENT_MSG_ID, m.getParent()); addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); + setPreviousMsgId(txn, m.getMessage().getGroupId(), + m.getMessage().getId()); trackOutgoingMessage(txn, m.getMessage()); txn.setComplete(); } catch (FormatException e) { @@ -181,6 +187,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements m.getMessage().getTimestamp(), m.getMember(), OURSELVES, 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_ID, m.getMember().getId()); + meta.put(KEY_AUTHOR_NAME, m.getMember().getName()); + meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey()); + } + @Override public PrivateGroup getPrivateGroup(GroupId g) throws DbException { PrivateGroup privateGroup; @@ -288,8 +303,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements throws DbException, FormatException { MessageId parentId = null; - if (meta.containsKey(KEY_PARENT_ID)) { - parentId = new MessageId(meta.getRaw(KEY_PARENT_ID)); + if (meta.containsKey(KEY_PARENT_MSG_ID)) { + parentId = new MessageId(meta.getRaw(KEY_PARENT_MSG_ID)); } long timestamp = meta.getLong(KEY_TIMESTAMP); @@ -318,23 +333,86 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { - 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. - // TODO JOIN new_member_id must be the identifier of a NEW_MEMBER message with the same member_name and member_public_key - - 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_ID, m.getMember().getId()); - meta.put(KEY_AUTHOR_NAME, m.getMember().getName()); - meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey()); + long timestamp = meta.getLong(KEY_TIMESTAMP); + MessageType type = + MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); + switch (type) { + case NEW_MEMBER: + // don't track incoming message, because it won't show in the UI + return true; + case JOIN: + // new_member_id must be the identifier of a NEW_MEMBER message + byte[] newMemberIdBytes = + meta.getOptionalRaw(KEY_NEW_MEMBER_MSG_ID); + MessageId newMemberId = new MessageId(newMemberIdBytes); + BdfDictionary newMemberMeta = clientHelper + .getMessageMetadataAsDictionary(txn, newMemberId); + MessageType newMemberType = MessageType + .valueOf(newMemberMeta.getLong(KEY_TYPE).intValue()); + if (newMemberType != NEW_MEMBER) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + // timestamp must be equal to timestamp of NEW_MEMBER message + if (timestamp != newMemberMeta.getLong(KEY_TIMESTAMP)) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + // NEW_MEMBER must have same member_name and member_public_key + if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID), + newMemberMeta.getRaw(KEY_AUTHOR_ID))) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + // TODO add to member list + trackIncomingMessage(txn, m); + return true; + case POST: + // timestamp must be greater than the timestamps of parent post + byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID); + if (parentIdBytes != null) { + MessageId parentId = new MessageId(parentIdBytes); + BdfDictionary parentMeta = clientHelper + .getMessageMetadataAsDictionary(txn, parentId); + if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP)) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + MessageType parentType = MessageType + .valueOf(parentMeta.getLong(KEY_TYPE).intValue()); + if (parentType != POST) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + } + // and the member's previous message + byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID); + MessageId previousMsgId = new MessageId(previousMsgIdBytes); + BdfDictionary previousMeta = clientHelper + .getMessageMetadataAsDictionary(txn, previousMsgId); + if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP)) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + // previous message must be from same member + if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID), + previousMeta.getRaw(KEY_AUTHOR_ID))) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + trackIncomingMessage(txn, m); + return true; + default: + // the validator should only let valid types pass + throw new RuntimeException("Unknown MessageType"); + } } } From c79ce61f6dfe849ef084e1f5fc66e3f92030d8d1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 24 Oct 2016 12:34:50 -0200 Subject: [PATCH 09/11] Add PrivateGroupManager integration tests --- .../org/briarproject/BlogManagerTest.java | 4 +- .../briarproject/PrivateGroupManagerTest.java | 544 ++++++++++++++++++ .../PrivateGroupManagerTestComponent.java | 81 +++ 3 files changed, 627 insertions(+), 2 deletions(-) create mode 100644 briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java create mode 100644 briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java index 60a1ba3c3..0339413b1 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java @@ -61,7 +61,7 @@ import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class BlogManagerTest { +public class BlogManagerTest extends BriarIntegrationTest { private LifecycleManager lifecycleManager0, lifecycleManager1; private SyncSessionFactory sync0, sync1; @@ -94,7 +94,7 @@ public class BlogManagerTest { private final String AUTHOR2 = "Author 2"; private static final Logger LOG = - Logger.getLogger(ForumSharingIntegrationTest.class.getName()); + Logger.getLogger(BlogManagerTest.class.getName()); private BlogManagerTestComponent t0, t1; diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java new file mode 100644 index 000000000..00609e4eb --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java @@ -0,0 +1,544 @@ +package org.briarproject; + +import net.jodah.concurrentunit.Waiter; + +import org.briarproject.api.clients.MessageTracker.GroupCount; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.KeyPair; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventListener; +import org.briarproject.api.event.MessageStateChangedEvent; +import org.briarproject.api.identity.AuthorFactory; +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.JoinMessageHeader; +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.sync.MessageId; +import org.briarproject.api.sync.SyncSession; +import org.briarproject.api.sync.SyncSessionFactory; +import org.briarproject.api.system.Clock; +import org.briarproject.contact.ContactModule; +import org.briarproject.crypto.CryptoModule; +import org.briarproject.lifecycle.LifecycleModule; +import org.briarproject.privategroup.PrivateGroupModule; +import org.briarproject.properties.PropertiesModule; +import org.briarproject.sync.SyncModule; +import org.briarproject.transport.TransportModule; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static org.briarproject.TestPluginsModule.MAX_LATENCY; +import static org.briarproject.api.identity.Author.Status.VERIFIED; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; +import static org.briarproject.api.sync.ValidationManager.State.INVALID; +import static org.briarproject.api.sync.ValidationManager.State.PENDING; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PrivateGroupManagerTest extends BriarIntegrationTest { + + private LifecycleManager lifecycleManager0, lifecycleManager1; + private SyncSessionFactory sync0, sync1; + private PrivateGroupManager groupManager0, groupManager1; + private ContactManager contactManager0, contactManager1; + private ContactId contactId0, contactId1; + private IdentityManager identityManager0, identityManager1; + private LocalAuthor author0, author1; + private PrivateGroup privateGroup0; + private GroupId groupId0; + + @Inject + Clock clock; + @Inject + AuthorFactory authorFactory; + @Inject + CryptoComponent crypto; + @Inject + PrivateGroupFactory privateGroupFactory; + @Inject + GroupMessageFactory groupMessageFactory; + + // objects accessed from background threads need to be volatile + private volatile Waiter validationWaiter; + private volatile Waiter deliveryWaiter; + + private final File testDir = TestUtils.getTestDirectory(); + private final SecretKey master = TestUtils.getSecretKey(); + private final int TIMEOUT = 15000; + private final String AUTHOR1 = "Author 1"; + private final String AUTHOR2 = "Author 2"; + + private static final Logger LOG = + Logger.getLogger(PrivateGroupManagerTest.class.getName()); + + private PrivateGroupManagerTestComponent t0, t1; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() throws Exception { + PrivateGroupManagerTestComponent component = + DaggerPrivateGroupManagerTestComponent.builder().build(); + component.inject(this); + injectEagerSingletons(component); + + assertTrue(testDir.mkdirs()); + File t0Dir = new File(testDir, AUTHOR1); + t0 = DaggerPrivateGroupManagerTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(t0Dir)).build(); + injectEagerSingletons(t0); + File t1Dir = new File(testDir, AUTHOR2); + t1 = DaggerPrivateGroupManagerTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); + injectEagerSingletons(t1); + + identityManager0 = t0.getIdentityManager(); + identityManager1 = t1.getIdentityManager(); + contactManager0 = t0.getContactManager(); + contactManager1 = t1.getContactManager(); + groupManager0 = t0.getPrivateGroupManager(); + groupManager1 = t1.getPrivateGroupManager(); + sync0 = t0.getSyncSessionFactory(); + sync1 = t1.getSyncSessionFactory(); + + // initialize waiters fresh for each test + validationWaiter = new Waiter(); + deliveryWaiter = new Waiter(); + + startLifecycles(); + } + + @Test + public void testSendingMessage() throws Exception { + defaultInit(); + + // create and add test message + long time = clock.currentTimeMillis(); + String body = "This is a test message!"; + MessageId previousMsgId = + groupManager0.getPreviousMsgId(groupId0); + GroupMessage msg = groupMessageFactory + .createGroupMessage(groupId0, time, null, author0, body, + previousMsgId); + groupManager0.addLocalMessage(msg); + assertEquals(msg.getMessage().getId(), + groupManager0.getPreviousMsgId(groupId0)); + + // sync test message + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + + // assert that message arrived as expected + Collection headers = + groupManager1.getHeaders(groupId0); + assertEquals(3, headers.size()); + GroupMessageHeader header = null; + for (GroupMessageHeader h : headers) { + if (!(h instanceof JoinMessageHeader)) { + header = h; + } + } + assertTrue(header != null); + assertFalse(header.isRead()); + assertEquals(author0, header.getAuthor()); + assertEquals(time, header.getTimestamp()); + assertEquals(VERIFIED, header.getAuthorStatus()); + assertEquals(body, groupManager1.getMessageBody(header.getId())); + GroupCount count = groupManager1.getGroupCount(groupId0); + assertEquals(2, count.getUnreadCount()); + assertEquals(time, count.getLatestMsgTime()); + assertEquals(3, count.getMsgCount()); + } + + @Test + public void testMessageWithWrongPreviousMsgId() throws Exception { + defaultInit(); + + // create and add test message with no previousMsgId + GroupMessage msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), null, + author0, "test", null); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with random previousMsgId + MessageId previousMsgId = new MessageId(TestUtils.getRandomId()); + msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), null, + author0, "test", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with wrong previousMsgId + previousMsgId = groupManager1.getPreviousMsgId(groupId0); + msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), null, + author0, "test", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + } + + @Test + public void testMessageWithWrongParentMsgId() throws Exception { + defaultInit(); + + // create and add test message with random parentMsgId + MessageId parentMsgId = new MessageId(TestUtils.getRandomId()); + MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0); + GroupMessage msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), + parentMsgId, author0, "test", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with wrong parentMsgId + parentMsgId = previousMsgId; + msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), + parentMsgId, author0, "test", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + } + + @Test + public void testMessageWithWrongTimestamp() throws Exception { + defaultInit(); + + // create and add test message with wrong timestamp + MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0); + GroupMessage msg = groupMessageFactory + .createGroupMessage(groupId0, 42, null, author0, "test", + previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with good timestamp + long time = clock.currentTimeMillis(); + msg = groupMessageFactory + .createGroupMessage(groupId0, time, null, author0, "test", + previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + assertEquals(3, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with same timestamp as previous message + previousMsgId = msg.getMessage().getId(); + msg = groupMessageFactory + .createGroupMessage(groupId0, time, previousMsgId, author0, + "test2", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(3, groupManager1.getHeaders(groupId0).size()); + } + + @Test + public void testWrongJoinMessages() throws Exception { + addDefaultIdentities(); + addDefaultContacts(); + listenToEvents(); + + // author0 joins privateGroup0 with later timestamp + long joinTime = clock.currentTimeMillis(); + GroupMessage newMemberMsg = groupMessageFactory + .createNewMemberMessage(groupId0, joinTime, author0, author0); + GroupMessage joinMsg = groupMessageFactory + .createJoinMessage(groupId0, joinTime + 1, author0, + newMemberMsg.getMessage().getId()); + groupManager0.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + assertEquals(joinMsg.getMessage().getId(), + groupManager0.getPreviousMsgId(groupId0)); + + // make group visible to 1 + Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); + t0.getDatabaseComponent() + .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), + true); + txn0.setComplete(); + t0.getDatabaseComponent().endTransaction(txn0); + + // author1 joins privateGroup0 and refers to wrong NEW_MEMBER message + joinMsg = groupMessageFactory + .createJoinMessage(groupId0, joinTime, author1, + newMemberMsg.getMessage().getId()); + joinTime = clock.currentTimeMillis(); + newMemberMsg = groupMessageFactory + .createNewMemberMessage(groupId0, joinTime, author0, author1); + groupManager1.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + assertEquals(joinMsg.getMessage().getId(), + groupManager1.getPreviousMsgId(groupId0)); + + // make group visible to 0 + Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); + t1.getDatabaseComponent() + .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), + true); + txn1.setComplete(); + t1.getDatabaseComponent().endTransaction(txn1); + + // sync join messages + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + validationWaiter.await(TIMEOUT, 1); + + // assert that 0 never joined the group from 1's perspective + assertEquals(1, groupManager1.getHeaders(groupId0).size()); + + sync1To0(); + deliveryWaiter.await(TIMEOUT, 1); + validationWaiter.await(TIMEOUT, 1); + + // assert that 1 never joined the group from 0's perspective + assertEquals(1, groupManager0.getHeaders(groupId0).size()); + } + + @After + public void tearDown() throws Exception { + stopLifecycles(); + TestUtils.deleteTestDirectory(testDir); + } + + private class Listener implements EventListener { + @Override + public void eventOccurred(Event e) { + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (!event.isLocal()) { + if (event.getState() == DELIVERED) { + LOG.info("Delivered new message"); + deliveryWaiter.resume(); + } else if (event.getState() == INVALID || + event.getState() == PENDING) { + LOG.info("Validated new " + event.getState().name() + + " message"); + validationWaiter.resume(); + } + } + } + } + } + + private void defaultInit() throws Exception { + addDefaultIdentities(); + addDefaultContacts(); + listenToEvents(); + addGroup(); + } + + private void addDefaultIdentities() throws DbException { + KeyPair keyPair0 = crypto.generateSignatureKeyPair(); + byte[] publicKey0 = keyPair0.getPublic().getEncoded(); + byte[] privateKey0 = keyPair0.getPrivate().getEncoded(); + author0 = authorFactory + .createLocalAuthor(AUTHOR1, publicKey0, privateKey0); + identityManager0.addLocalAuthor(author0); + privateGroup0 = + privateGroupFactory.createPrivateGroup("Testgroup", author0); + groupId0 = privateGroup0.getId(); + + KeyPair keyPair1 = crypto.generateSignatureKeyPair(); + byte[] publicKey1 = keyPair1.getPublic().getEncoded(); + byte[] privateKey1 = keyPair1.getPrivate().getEncoded(); + author1 = authorFactory + .createLocalAuthor(AUTHOR2, publicKey1, privateKey1); + identityManager1.addLocalAuthor(author1); + } + + private void addDefaultContacts() throws DbException { + // sharer adds invitee as contact + contactId1 = contactManager0.addContact(author1, + author0.getId(), master, clock.currentTimeMillis(), true, + true, true + ); + // invitee adds sharer back + contactId0 = contactManager1.addContact(author0, + author1.getId(), master, clock.currentTimeMillis(), true, + true, true + ); + } + + private void listenToEvents() { + Listener listener0 = new Listener(); + t0.getEventBus().addListener(listener0); + Listener listener1 = new Listener(); + t1.getEventBus().addListener(listener1); + } + + private void addGroup() throws Exception { + // author0 joins privateGroup0 + long joinTime = clock.currentTimeMillis(); + GroupMessage newMemberMsg = groupMessageFactory + .createNewMemberMessage(privateGroup0.getId(), joinTime, + author0, author0); + GroupMessage joinMsg = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author0, + newMemberMsg.getMessage().getId()); + groupManager0.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + assertEquals(joinMsg.getMessage().getId(), + groupManager0.getPreviousMsgId(groupId0)); + + // make group visible to 1 + Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); + t0.getDatabaseComponent() + .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), + true); + txn0.setComplete(); + t0.getDatabaseComponent().endTransaction(txn0); + + // author1 joins privateGroup0 + joinTime = clock.currentTimeMillis(); + newMemberMsg = groupMessageFactory + .createNewMemberMessage(privateGroup0.getId(), joinTime, + author0, author1); + joinMsg = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author1, + newMemberMsg.getMessage().getId()); + groupManager1.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + assertEquals(joinMsg.getMessage().getId(), + groupManager1.getPreviousMsgId(groupId0)); + + // make group visible to 0 + Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); + t1.getDatabaseComponent() + .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), + true); + txn1.setComplete(); + t1.getDatabaseComponent().endTransaction(txn1); + + // sync join messages + sync0To1(); + deliveryWaiter.await(TIMEOUT, 2); + sync1To0(); + deliveryWaiter.await(TIMEOUT, 2); + } + + private void sync0To1() throws IOException, TimeoutException { + deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); + } + + private void sync1To0() throws IOException, TimeoutException { + deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); + } + + private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId, + SyncSessionFactory toSync, ContactId toId, String debug) + throws IOException, TimeoutException { + + if (debug != null) LOG.info("TEST: Sending message from " + debug); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Create an outgoing sync session + SyncSession sessionFrom = + fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out); + // Write whatever needs to be written + sessionFrom.run(); + out.close(); + + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + // Create an incoming sync session + SyncSession sessionTo = toSync.createIncomingSession(fromId, in); + // Read whatever needs to be read + sessionTo.run(); + in.close(); + } + + private void startLifecycles() throws InterruptedException { + // Start the lifecycle manager and wait for it to finish + lifecycleManager0 = t0.getLifecycleManager(); + lifecycleManager1 = t1.getLifecycleManager(); + lifecycleManager0.startServices(); + lifecycleManager1.startServices(); + lifecycleManager0.waitForStartup(); + lifecycleManager1.waitForStartup(); + } + + private void stopLifecycles() throws InterruptedException { + // Clean up + lifecycleManager0.stopServices(); + lifecycleManager1.stopServices(); + lifecycleManager0.waitForShutdown(); + lifecycleManager1.waitForShutdown(); + } + + private void injectEagerSingletons( + PrivateGroupManagerTestComponent component) { + component.inject(new LifecycleModule.EagerSingletons()); + component.inject(new PrivateGroupModule.EagerSingletons()); + component.inject(new CryptoModule.EagerSingletons()); + component.inject(new ContactModule.EagerSingletons()); + component.inject(new TransportModule.EagerSingletons()); + component.inject(new SyncModule.EagerSingletons()); + component.inject(new PropertiesModule.EagerSingletons()); + } + +} diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java new file mode 100644 index 000000000..7461eb02b --- /dev/null +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java @@ -0,0 +1,81 @@ +package org.briarproject; + +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.sync.SyncSessionFactory; +import org.briarproject.clients.ClientsModule; +import org.briarproject.contact.ContactModule; +import org.briarproject.crypto.CryptoModule; +import org.briarproject.data.DataModule; +import org.briarproject.db.DatabaseModule; +import org.briarproject.event.EventModule; +import org.briarproject.identity.IdentityModule; +import org.briarproject.lifecycle.LifecycleModule; +import org.briarproject.privategroup.PrivateGroupModule; +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; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + TestDatabaseModule.class, + TestPluginsModule.class, + TestSeedProviderModule.class, + ClientsModule.class, + ContactModule.class, + CryptoModule.class, + DataModule.class, + DatabaseModule.class, + EventModule.class, + PrivateGroupModule.class, + IdentityModule.class, + LifecycleModule.class, + PropertiesModule.class, + SharingModule.class, + SyncModule.class, + SystemModule.class, + TransportModule.class +}) +interface PrivateGroupManagerTestComponent { + + void inject(PrivateGroupManagerTest testCase); + + void inject(ContactModule.EagerSingletons init); + + void inject(CryptoModule.EagerSingletons init); + + void inject(PrivateGroupModule.EagerSingletons init); + + void inject(LifecycleModule.EagerSingletons init); + + void inject(PropertiesModule.EagerSingletons init); + + void inject(SyncModule.EagerSingletons init); + + void inject(TransportModule.EagerSingletons init); + + LifecycleManager getLifecycleManager(); + + EventBus getEventBus(); + + IdentityManager getIdentityManager(); + + ContactManager getContactManager(); + + PrivateGroupManager getPrivateGroupManager(); + + SyncSessionFactory getSyncSessionFactory(); + + DatabaseComponent getDatabaseComponent(); + +} From c0aa255bb692bce8e7bc5fab2d28f92648370363 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 28 Oct 2016 16:07:17 -0200 Subject: [PATCH 10/11] Address review comments --- .../briarproject/PrivateGroupManagerTest.java | 27 +++-- .../PrivateGroupManagerTestComponent.java | 2 + .../conversation/GroupControllerImpl.java | 5 +- .../conversation/GroupMessageAdapter.java | 8 +- .../conversation/GroupMessageItem.java | 13 +++ .../conversation/JoinMessageItem.java | 13 +++ .../conversation/JoinMessageItemHolder.java | 30 ++++++ .../threaded/BaseThreadItemViewHolder.java | 4 +- .../android/threaded/ThreadItem.java | 4 +- .../android/threaded/ThreadItemAdapter.java | 2 +- .../threaded/ThreadListController.java | 3 - .../threaded/ThreadListControllerImpl.java | 1 - .../api/clients/ClientHelper.java | 2 +- .../api/privategroup/MessageType.java | 14 +-- .../api/privategroup/PrivateGroupManager.java | 8 +- .../briarproject/blogs/BlogPostValidator.java | 13 ++- .../clients/ClientHelperImpl.java | 27 ++--- .../forum/ForumPostValidator.java | 10 +- .../briarproject/privategroup/Constants.java | 6 +- .../privategroup/GroupMessageFactoryImpl.java | 22 ++-- .../privategroup/GroupMessageValidator.java | 102 ++++++++++-------- .../privategroup/PrivateGroupManagerImpl.java | 42 ++++---- 22 files changed, 226 insertions(+), 132 deletions(-) create mode 100644 briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java index 00609e4eb..18a6d5eb4 100644 --- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java @@ -72,6 +72,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { private LocalAuthor author0, author1; private PrivateGroup privateGroup0; private GroupId groupId0; + private GroupMessage newMemberMsg0; @Inject Clock clock; @@ -221,6 +222,20 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + // create and add test message with previousMsgId of newMemberMsg + previousMsgId = newMemberMsg0.getMessage().getId(); + msg = groupMessageFactory + .createGroupMessage(groupId0, clock.currentTimeMillis(), null, + author0, "test", previousMsgId); + groupManager0.addLocalMessage(msg); + + // sync test message + sync0To1(); + validationWaiter.await(TIMEOUT, 1); + + // assert that message did not arrive + assertEquals(2, groupManager1.getHeaders(groupId0).size()); } @Test @@ -437,13 +452,13 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { private void addGroup() throws Exception { // author0 joins privateGroup0 long joinTime = clock.currentTimeMillis(); - GroupMessage newMemberMsg = groupMessageFactory + newMemberMsg0 = groupMessageFactory .createNewMemberMessage(privateGroup0.getId(), joinTime, author0, author0); GroupMessage joinMsg = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author0, - newMemberMsg.getMessage().getId()); - groupManager0.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + newMemberMsg0.getMessage().getId()); + groupManager0.addPrivateGroup(privateGroup0, newMemberMsg0, joinMsg); assertEquals(joinMsg.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); @@ -457,13 +472,13 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // author1 joins privateGroup0 joinTime = clock.currentTimeMillis(); - newMemberMsg = groupMessageFactory + GroupMessage newMemberMsg1 = groupMessageFactory .createNewMemberMessage(privateGroup0.getId(), joinTime, author0, author1); joinMsg = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, - newMemberMsg.getMessage().getId()); - groupManager1.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); + newMemberMsg1.getMessage().getId()); + groupManager1.addPrivateGroup(privateGroup0, newMemberMsg1, joinMsg); assertEquals(joinMsg.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java index 7461eb02b..68191f4f8 100644 --- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java @@ -15,6 +15,7 @@ import org.briarproject.db.DatabaseModule; import org.briarproject.event.EventModule; import org.briarproject.identity.IdentityModule; import org.briarproject.lifecycle.LifecycleModule; +import org.briarproject.messaging.MessagingModule; import org.briarproject.privategroup.PrivateGroupModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.sharing.SharingModule; @@ -37,6 +38,7 @@ import dagger.Component; DataModule.class, DatabaseModule.class, EventModule.class, + MessagingModule.class, PrivateGroupModule.class, IdentityModule.class, LifecycleModule.class, 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 8d61bbff8..abbf10d42 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -2,7 +2,6 @@ package org.briarproject.android.privategroup.conversation; import android.support.annotation.Nullable; -import org.briarproject.R; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.threaded.ThreadListControllerImpl; @@ -95,8 +94,8 @@ public class GroupControllerImpl extends protected String loadMessageBody(GroupMessageHeader header) throws DbException { if (header instanceof JoinMessageHeader) { - return listener.getApplicationContext() - .getString(R.string.groups_member_joined); + // will be looked up later + return ""; } return privateGroupManager.getMessageBody(header.getId()); } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java index 2a9c75a00..58eec2938 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java @@ -1,5 +1,6 @@ package org.briarproject.android.privategroup.conversation; +import android.support.annotation.LayoutRes; import android.support.annotation.UiThread; import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; @@ -19,12 +20,11 @@ public class GroupMessageAdapter extends ThreadItemAdapter { super(listener, layoutManager); } + @LayoutRes @Override public int getItemViewType(int position) { GroupMessageItem item = getVisibleItem(position); - if (item instanceof JoinMessageItem) { - return R.layout.list_item_thread_notice; - } + if (item != null) return item.getLayout(); return R.layout.list_item_thread; } @@ -34,7 +34,7 @@ public class GroupMessageAdapter extends ThreadItemAdapter { View v = LayoutInflater.from(parent.getContext()) .inflate(type, parent, false); if (type == R.layout.list_item_thread_notice) { - return new BaseThreadItemViewHolder<>(v); + return new JoinMessageItemHolder(v); } return new ThreadItemViewHolder<>(v); } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java index b961fd3f6..9deb0424e 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java @@ -1,11 +1,19 @@ package org.briarproject.android.privategroup.conversation; +import android.support.annotation.LayoutRes; +import android.support.annotation.UiThread; + +import org.briarproject.R; import org.briarproject.android.threaded.ThreadItem; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.sync.MessageId; +import javax.annotation.concurrent.NotThreadSafe; + +@UiThread +@NotThreadSafe class GroupMessageItem extends ThreadItem { private GroupMessageItem(MessageId messageId, MessageId parentId, @@ -19,4 +27,9 @@ class GroupMessageItem extends ThreadItem { h.getAuthorStatus(), h.isRead()); } + @LayoutRes + public int getLayout() { + return R.layout.list_item_thread; + } + } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java index e21399127..44c732ffd 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java @@ -1,7 +1,15 @@ package org.briarproject.android.privategroup.conversation; +import android.support.annotation.LayoutRes; +import android.support.annotation.UiThread; + +import org.briarproject.R; import org.briarproject.api.privategroup.GroupMessageHeader; +import javax.annotation.concurrent.NotThreadSafe; + +@UiThread +@NotThreadSafe class JoinMessageItem extends GroupMessageItem { JoinMessageItem(GroupMessageHeader h, @@ -19,4 +27,9 @@ class JoinMessageItem extends GroupMessageItem { return false; } + @LayoutRes + public int getLayout() { + return R.layout.list_item_thread_notice; + } + } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java new file mode 100644 index 000000000..8a2942a4c --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java @@ -0,0 +1,30 @@ +package org.briarproject.android.privategroup.conversation; + +import android.support.annotation.UiThread; +import android.view.View; + +import org.briarproject.R; +import org.briarproject.android.threaded.BaseThreadItemViewHolder; +import org.briarproject.android.threaded.ThreadItemAdapter; +import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener; +import org.briarproject.api.nullsafety.NotNullByDefault; + +@UiThread +@NotNullByDefault +public class JoinMessageItemHolder + extends BaseThreadItemViewHolder { + + public JoinMessageItemHolder(View v) { + super(v); + } + + @Override + public void bind(final ThreadItemAdapter adapter, + final ThreadItemListener listener, + final GroupMessageItem item, int pos) { + super.bind(adapter, listener, item, pos); + + textView.setText(getContext().getString(R.string.groups_member_joined)); + } + +} diff --git a/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java b/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java index 47ed3502f..af8e800b3 100644 --- a/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java +++ b/briar-android/src/org/briarproject/android/threaded/BaseThreadItemViewHolder.java @@ -21,13 +21,13 @@ import org.briarproject.util.StringUtils; @UiThread @NotNullByDefault -public class BaseThreadItemViewHolder +public abstract class BaseThreadItemViewHolder extends RecyclerView.ViewHolder { private final static int ANIMATION_DURATION = 5000; + protected final TextView textView; private final ViewGroup layout; - private final TextView textView; private final AuthorView author; private final View topDivider; diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItem.java b/briar-android/src/org/briarproject/android/threaded/ThreadItem.java index c4281e9ac..e4c055e2b 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItem.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItem.java @@ -1,7 +1,5 @@ package org.briarproject.android.threaded; -import android.support.annotation.UiThread; - import org.briarproject.api.clients.MessageTree.MessageNode; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; @@ -11,7 +9,6 @@ import javax.annotation.concurrent.NotThreadSafe; import static org.briarproject.android.threaded.ThreadItemAdapter.UNDEFINED; -@UiThread @NotThreadSafe public abstract class ThreadItem implements MessageNode { @@ -97,4 +94,5 @@ public abstract class ThreadItem implements MessageNode { public void setDescendantCount(int descendantCount) { this.descendantCount = descendantCount; } + } diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index 9e93f8f07..21425ced3 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -316,7 +316,7 @@ public class ThreadItemAdapter revision++; } - protected interface ThreadItemListener { + public interface ThreadItemListener { void onItemVisible(I item); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java index 43ce1d5c2..f2e7570a8 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListController.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListController.java @@ -1,6 +1,5 @@ package org.briarproject.android.threaded; -import android.content.Context; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -40,8 +39,6 @@ public interface ThreadListController dependencies = - Collections.singleton(new MessageId(new_member_id)); + Collections.singleton(new MessageId(newMemberId)); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_NEW_MEMBER_MSG_ID, new_member_id); + meta.put(KEY_NEW_MEMBER_MSG_ID, newMemberId); return new BdfMessageContext(meta, dependencies); } private BdfMessageContext validatePost(Message m, Group g, BdfList body, - String member_name, byte[] member_public_key) + String memberName, byte[] memberPublicKey) throws InvalidMessageException, FormatException { // The content is a BDF list with six elements @@ -158,15 +171,13 @@ class GroupMessageValidator extends BdfMessageValidator { // parent_id (raw or null) // the identifier of the post to which this is a reply, if any - byte[] parent_id = body.getOptionalRaw(2); - if (parent_id != null) { - checkLength(parent_id, MessageId.LENGTH); - } + byte[] parentId = body.getOptionalRaw(2); + checkLength(parentId, MessageId.LENGTH); // previous_message_id (raw) // the identifier of the member's previous post or join message - byte[] previous_message_id = body.getRaw(3); - checkLength(previous_message_id, MessageId.LENGTH); + byte[] previousMessageId = body.getRaw(3); + checkLength(previousMessageId, MessageId.LENGTH); // content (string) String content = body.getString(4); @@ -178,20 +189,25 @@ class GroupMessageValidator extends BdfMessageValidator { checkLength(signature, 1, MAX_SIGNATURE_LENGTH); // Verify Signature - BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, - member_public_key, parent_id, previous_message_id, content); - clientHelper.verifySignature(signature, member_public_key, signed); + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), POST.getInt(), + memberName, memberPublicKey, parentId, previousMessageId, + content); + try { + clientHelper.verifySignature(signature, memberPublicKey, signed); + } catch (GeneralSecurityException e) { + throw new InvalidMessageException(e); + } // The parent post, if any, // and the member's previous message are dependencies Collection dependencies = new ArrayList(); - if (parent_id != null) dependencies.add(new MessageId(parent_id)); - dependencies.add(new MessageId(previous_message_id)); + if (parentId != null) dependencies.add(new MessageId(parentId)); + dependencies.add(new MessageId(previousMessageId)); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - if (parent_id != null) meta.put(KEY_PARENT_MSG_ID, parent_id); - meta.put(KEY_PREVIOUS_MSG_ID, previous_message_id); + if (parentId != null) meta.put(KEY_PARENT_MSG_ID, parentId); + meta.put(KEY_PREVIOUS_MSG_ID, previousMessageId); return new BdfMessageContext(meta, dependencies); } @@ -200,9 +216,9 @@ class GroupMessageValidator extends BdfMessageValidator { c.getDictionary().put(KEY_TIMESTAMP, time); c.getDictionary().put(KEY_READ, false); Author a = authorFactory.createAuthor(authorName, pubKey); - c.getDictionary().put(KEY_AUTHOR_ID, a.getId()); - c.getDictionary().put(KEY_AUTHOR_NAME, authorName); - c.getDictionary().put(KEY_AUTHOR_PUBLIC_KEY, pubKey); + c.getDictionary().put(KEY_MEMBER_ID, a.getId()); + c.getDictionary().put(KEY_MEMBER_NAME, authorName); + c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, pubKey); } } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index eb3a888f0..769e065b8 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -44,9 +44,9 @@ import static org.briarproject.api.identity.Author.Status.OURSELVES; import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; import static org.briarproject.api.privategroup.MessageType.POST; -import static org.briarproject.privategroup.Constants.KEY_AUTHOR_ID; -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_MEMBER_ID; +import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; +import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; @@ -141,8 +141,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements 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(); + byte[] previousMsgIdBytes = d.getRaw(KEY_PREVIOUS_MSG_ID); return new MessageId(previousMsgIdBytes); } @@ -191,9 +190,9 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements boolean read) { meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp()); meta.put(KEY_READ, read); - meta.put(KEY_AUTHOR_ID, m.getMember().getId()); - meta.put(KEY_AUTHOR_NAME, m.getMember().getName()); - meta.put(KEY_AUTHOR_PUBLIC_KEY, m.getMember().getPublicKey()); + meta.put(KEY_MEMBER_ID, m.getMember().getId()); + meta.put(KEY_MEMBER_NAME, m.getMember().getName()); + meta.put(KEY_MEMBER_PUBLIC_KEY, m.getMember().getPublicKey()); } @Override @@ -269,11 +268,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements clientHelper.getMessageMetadataAsDictionary(txn, g); // get all authors we need to get the status for Set authors = new HashSet(); - for (Entry entry : metadata.entrySet()) { - BdfDictionary meta = entry.getValue(); + for (BdfDictionary meta : metadata.values()) { if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt()) continue; - byte[] idBytes = meta.getRaw(KEY_AUTHOR_ID); + byte[] idBytes = meta.getRaw(KEY_MEMBER_ID); authors.add(new AuthorId(idBytes)); } // get statuses for all authors @@ -308,9 +306,9 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } long timestamp = meta.getLong(KEY_TIMESTAMP); - AuthorId authorId = new AuthorId(meta.getRaw(KEY_AUTHOR_ID)); - String name = meta.getString(KEY_AUTHOR_NAME); - byte[] publicKey = meta.getRaw(KEY_AUTHOR_PUBLIC_KEY); + AuthorId authorId = new AuthorId(meta.getRaw(KEY_MEMBER_ID)); + String name = meta.getString(KEY_MEMBER_NAME); + byte[] publicKey = meta.getRaw(KEY_MEMBER_PUBLIC_KEY); Author author = new Author(authorId, name, publicKey); Status status; @@ -361,8 +359,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return false; } // NEW_MEMBER must have same member_name and member_public_key - if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID), - newMemberMeta.getRaw(KEY_AUTHOR_ID))) { + if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID), + newMemberMeta.getRaw(KEY_MEMBER_ID))) { // FIXME throw new InvalidMessageException() (#643) db.deleteMessage(txn, m.getId()); return false; @@ -401,8 +399,16 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return false; } // previous message must be from same member - if (!Arrays.equals(meta.getRaw(KEY_AUTHOR_ID), - previousMeta.getRaw(KEY_AUTHOR_ID))) { + if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID), + previousMeta.getRaw(KEY_MEMBER_ID))) { + // FIXME throw new InvalidMessageException() (#643) + db.deleteMessage(txn, m.getId()); + return false; + } + // previous message must be a POST or JOIN + MessageType previousType = MessageType + .valueOf(previousMeta.getLong(KEY_TYPE).intValue()); + if (previousType != JOIN && previousType != POST) { // FIXME throw new InvalidMessageException() (#643) db.deleteMessage(txn, m.getId()); return false; From 656a947f5a44f616eb1964ffa59682d68a97e45f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 31 Oct 2016 13:13:59 -0200 Subject: [PATCH 11/11] Last minor review comments addressed --- .../src/org/briarproject/android/forum/ForumActivity.java | 2 +- .../android/privategroup/conversation/GroupActivity.java | 2 +- .../privategroup/conversation/GroupMessageAdapter.java | 6 +++--- ...ssageItemHolder.java => JoinMessageItemViewHolder.java} | 4 ++-- .../briarproject/android/threaded/ThreadItemAdapter.java | 2 +- .../briarproject/android/threaded/ThreadListActivity.java | 7 ++++--- ...ThreadItemViewHolder.java => ThreadPostViewHolder.java} | 4 ++-- 7 files changed, 14 insertions(+), 13 deletions(-) rename briar-android/src/org/briarproject/android/privategroup/conversation/{JoinMessageItemHolder.java => JoinMessageItemViewHolder.java} (90%) rename briar-android/src/org/briarproject/android/threaded/{ThreadItemViewHolder.java => ThreadPostViewHolder.java} (96%) diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 31816fa8f..36d552473 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -36,7 +36,7 @@ import static android.widget.Toast.LENGTH_SHORT; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; public class ForumActivity extends - ThreadListActivity> { + ThreadListActivity { private static final int REQUEST_FORUM_SHARED = 3; diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java index 71a4865b5..d17ed8d66 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java @@ -22,7 +22,7 @@ import javax.inject.Inject; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; public class GroupActivity extends - ThreadListActivity { + ThreadListActivity { @Inject GroupController controller; diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java index 58eec2938..c042a1829 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java @@ -10,7 +10,7 @@ import android.view.ViewGroup; import org.briarproject.R; import org.briarproject.android.threaded.BaseThreadItemViewHolder; import org.briarproject.android.threaded.ThreadItemAdapter; -import org.briarproject.android.threaded.ThreadItemViewHolder; +import org.briarproject.android.threaded.ThreadPostViewHolder; @UiThread public class GroupMessageAdapter extends ThreadItemAdapter { @@ -34,9 +34,9 @@ public class GroupMessageAdapter extends ThreadItemAdapter { View v = LayoutInflater.from(parent.getContext()) .inflate(type, parent, false); if (type == R.layout.list_item_thread_notice) { - return new JoinMessageItemHolder(v); + return new JoinMessageItemViewHolder(v); } - return new ThreadItemViewHolder<>(v); + return new ThreadPostViewHolder<>(v); } } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java similarity index 90% rename from briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java rename to briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java index 8a2942a4c..972a6149e 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemHolder.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java @@ -11,10 +11,10 @@ import org.briarproject.api.nullsafety.NotNullByDefault; @UiThread @NotNullByDefault -public class JoinMessageItemHolder +public class JoinMessageItemViewHolder extends BaseThreadItemViewHolder { - public JoinMessageItemHolder(View v) { + public JoinMessageItemViewHolder(View v) { super(v); } diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index 21425ced3..7c3ee9079 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -50,7 +50,7 @@ public class ThreadItemAdapter ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.list_item_thread, parent, false); - return new ThreadItemViewHolder<>(v); + return new ThreadPostViewHolder<>(v); } @Override diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java index 268fdcca9..eff17e175 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java @@ -35,7 +35,7 @@ import static android.support.design.widget.Snackbar.make; import static android.view.View.GONE; import static android.view.View.VISIBLE; -public abstract class ThreadListActivity> +public abstract class ThreadListActivity extends BriarActivity implements ThreadListListener, TextInputListener, ThreadItemListener { @@ -46,7 +46,7 @@ public abstract class ThreadListActivity adapter; protected BriarRecyclerView list; protected TextInputView textInput; protected GroupId groupId; @@ -88,7 +88,8 @@ public abstract class ThreadListActivity createAdapter( + LinearLayoutManager layoutManager); protected void loadNamedGroup() { getController().loadNamedGroup( diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java b/briar-android/src/org/briarproject/android/threaded/ThreadPostViewHolder.java similarity index 96% rename from briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java rename to briar-android/src/org/briarproject/android/threaded/ThreadPostViewHolder.java index c59eac8a4..bffd1d6ea 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemViewHolder.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadPostViewHolder.java @@ -14,14 +14,14 @@ import static android.view.View.VISIBLE; @UiThread @NotNullByDefault -public class ThreadItemViewHolder +public class ThreadPostViewHolder extends BaseThreadItemViewHolder { private final TextView lvlText, repliesText; private final View[] lvls; private final View chevron, replyButton; - public ThreadItemViewHolder(View v) { + public ThreadPostViewHolder(View v) { super(v); lvlText = (TextView) v.findViewById(R.id.nested_line_text);