From 743fc7dd1fc5c66237ba9d49cccd72aa986a1ebf Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 16 Aug 2016 10:56:51 -0300 Subject: [PATCH 1/3] Do not require an entire Message for the MessageSharedEvent --- .../org/briarproject/api/clients/ClientHelper.java | 2 +- .../org/briarproject/api/db/DatabaseComponent.java | 2 +- .../briarproject/api/event/MessageSharedEvent.java | 12 ++++++------ .../org/briarproject/clients/ClientHelperImpl.java | 2 +- .../org/briarproject/db/DatabaseComponentImpl.java | 8 ++++---- .../src/org/briarproject/forum/ForumManagerImpl.java | 2 +- .../org/briarproject/blogs/BlogManagerImplTest.java | 2 +- .../briarproject/db/DatabaseComponentImplTest.java | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/briar-api/src/org/briarproject/api/clients/ClientHelper.java b/briar-api/src/org/briarproject/api/clients/ClientHelper.java index a3524d276..d4625af66 100644 --- a/briar-api/src/org/briarproject/api/clients/ClientHelper.java +++ b/briar-api/src/org/briarproject/api/clients/ClientHelper.java @@ -72,7 +72,7 @@ public interface ClientHelper { /** * Marks the given message as shared or unshared with other contacts. */ - void setMessageShared(Transaction txn, Message m, boolean shared) + void setMessageShared(Transaction txn, MessageId m, boolean shared) throws DbException; byte[] toByteArray(BdfDictionary dictionary) throws FormatException; diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 735f64a21..d3dbace30 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -437,7 +437,7 @@ public interface DatabaseComponent { /** * Marks the given message as shared or unshared. */ - void setMessageShared(Transaction txn, Message m, boolean shared) + void setMessageShared(Transaction txn, MessageId m, boolean shared) throws DbException; /** diff --git a/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java index d011e8c3e..840c7cc42 100644 --- a/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java +++ b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java @@ -1,17 +1,17 @@ package org.briarproject.api.event; -import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; /** An event that is broadcast when a message is shared. */ public class MessageSharedEvent extends Event { - private final Message message; + private final MessageId messageId; - public MessageSharedEvent(Message message) { - this.message = message; + public MessageSharedEvent(MessageId message) { + this.messageId = message; } - public Message getMessage() { - return message; + public MessageId getMessageId() { + return messageId; } } diff --git a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java index 00778b6b6..5612e06b8 100644 --- a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java +++ b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java @@ -240,7 +240,7 @@ class ClientHelperImpl implements ClientHelper { } @Override - public void setMessageShared(Transaction txn, Message m, boolean shared) + public void setMessageShared(Transaction txn, MessageId m, boolean shared) throws DbException { db.setMessageShared(txn, m, shared); } diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 1039a69e0..01a2d9a96 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -199,7 +199,7 @@ class DatabaseComponentImpl implements DatabaseComponent { transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageStateChangedEvent(m, c, true, DELIVERED)); - if (shared) transaction.attach(new MessageSharedEvent(m)); + if (shared) transaction.attach(new MessageSharedEvent(m.getId())); } db.mergeMessageMetadata(txn, m.getId(), meta); } @@ -704,13 +704,13 @@ class DatabaseComponentImpl implements DatabaseComponent { transaction.attach(new ContactStatusChangedEvent(c, active)); } - public void setMessageShared(Transaction transaction, Message m, + public void setMessageShared(Transaction transaction, MessageId m, boolean shared) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); - if (!db.containsMessage(txn, m.getId())) + if (!db.containsMessage(txn, m)) throw new NoSuchMessageException(); - db.setMessageShared(txn, m.getId(), shared); + db.setMessageShared(txn, m, shared); if (shared) transaction.attach(new MessageSharedEvent(m)); } diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index 84f28dbf9..b33842fc3 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -78,7 +78,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { protected void incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { - clientHelper.setMessageShared(txn, m, true); + clientHelper.setMessageShared(txn, m.getId(), true); ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta); ForumPostReceivedEvent event = diff --git a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java index 917110330..637be2dbf 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java @@ -192,7 +192,7 @@ public class BlogManagerImplTest extends BriarTestCase { ); context.checking(new Expectations() {{ - oneOf(clientHelper).setMessageShared(txn, message, true); + oneOf(clientHelper).setMessageShared(txn, messageId, true); oneOf(identityManager) .getAuthorStatus(txn, blog1.getAuthor().getId()); will(returnValue(VERIFIED)); diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index 30f46c032..123ca114c 100644 --- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java @@ -751,7 +751,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { transaction = db.startTransaction(false); try { - db.setMessageShared(transaction, message, true); + db.setMessageShared(transaction, message.getId(), true); fail(); } catch (NoSuchMessageException expected) { // Expected From 3dd3a18694db7662dfa4c00b1282e3ee546b75e7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 11 Aug 2016 19:34:52 -0300 Subject: [PATCH 2/3] Add support for comments and reblogging to Blog Client Comments and reblogs need to depend on the post they refer to. Since message dependencies are limited to one group, the post and also the comments need to be wrapped when commented on or reblogged to another blog. For this reason, in addition to comments, two new wrapping message types are introduced. They retain all data of the original messages and allow for reconstruction and signature verification. This commit breaks backwards compatibility with old blog posts. It removes the content type, title and parent ID from the post message structure. --- .../org/briarproject/BlogManagerTest.java | 259 ++++++++++++++- .../android/blogs/BlogControllerImpl.java | 3 +- .../android/blogs/BlogPostFragment.java | 7 +- .../android/blogs/BlogPostItem.java | 10 - .../android/blogs/WriteBlogPostActivity.java | 32 +- .../android/forum/ForumControllerImpl.java | 2 +- .../api/blogs/BlogCommentHeader.java | 43 +++ .../briarproject/api/blogs/BlogConstants.java | 11 +- .../briarproject/api/blogs/BlogManager.java | 8 +- .../org/briarproject/api/blogs/BlogPost.java | 17 +- .../api/blogs/BlogPostFactory.java | 31 +- .../api/blogs/BlogPostHeader.java | 36 ++- .../api/clients/ClientHelper.java | 6 + .../briarproject/api/clients/PostHeader.java | 14 +- .../api/forum/ForumConstants.java | 1 - .../org/briarproject/api/forum/ForumPost.java | 9 +- .../api/forum/ForumPostHeader.java | 5 +- .../briarproject/blogs/BlogFactoryImpl.java | 2 +- .../briarproject/blogs/BlogManagerImpl.java | 299 +++++++++++++++--- .../blogs/BlogPostFactoryImpl.java | 133 ++++++-- .../briarproject/blogs/BlogPostValidator.java | 118 +++---- .../org/briarproject/blogs/BlogsModule.java | 6 +- .../clients/ClientHelperImpl.java | 26 +- .../briarproject/clients/ClientsModule.java | 5 +- .../briarproject/feed/FeedManagerImpl.java | 15 +- .../briarproject/forum/ForumManagerImpl.java | 7 +- .../forum/ForumPostFactoryImpl.java | 4 +- .../blogs/BlogManagerImplTest.java | 24 +- .../blogs/BlogPostValidatorTest.java | 61 ++-- 29 files changed, 874 insertions(+), 320 deletions(-) create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.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 09e63bd98..e2893714d 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java @@ -3,6 +3,7 @@ package org.briarproject; import net.jodah.concurrentunit.Waiter; import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogCommentHeader; import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogPost; @@ -31,6 +32,7 @@ import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.sync.SyncModule; import org.briarproject.transport.TransportModule; +import org.briarproject.util.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -50,6 +52,10 @@ import javax.inject.Inject; import static junit.framework.Assert.assertFalse; import static org.briarproject.TestPluginsModule.MAX_LATENCY; +import static org.briarproject.api.blogs.MessageType.COMMENT; +import static org.briarproject.api.blogs.MessageType.POST; +import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; +import static org.briarproject.api.blogs.MessageType.WRAPPED_POST; 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; @@ -89,7 +95,6 @@ public class BlogManagerTest { private final int TIMEOUT = 15000; private final String AUTHOR1 = "Author 1"; private final String AUTHOR2 = "Author 2"; - private final String CONTENT_TYPE = "text/plain"; private static final Logger LOG = Logger.getLogger(ForumSharingIntegrationTest.class.getName()); @@ -172,15 +177,15 @@ public class BlogManagerTest { defaultInit(); // check that blog0 has no posts - final byte[] body = TestUtils.getRandomBytes(42); + final String body = TestUtils.getRandomString(42); Collection headers0 = blogManager0.getPostHeaders(blog0.getId()); assertEquals(0, headers0.size()); // add a post to blog0 BlogPost p = blogPostFactory - .createBlogPost(blog0.getId(), null, clock.currentTimeMillis(), - null, author0, CONTENT_TYPE, body); + .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null, + author0, body); blogManager0.addLocalPost(p); // check that post is now in blog0 @@ -188,7 +193,7 @@ public class BlogManagerTest { assertEquals(1, headers0.size()); // check that body is there - assertArrayEquals(body, + assertArrayEquals(StringUtils.toUtf8(body), blogManager0.getPostBody(p.getMessage().getId())); // make sure that blog0 at author1 doesn't have the post yet @@ -203,9 +208,10 @@ public class BlogManagerTest { // make sure post arrived headers1 = blogManager1.getPostHeaders(blog0.getId()); assertEquals(1, headers1.size()); + assertEquals(POST, headers1.iterator().next().getType()); // check that body is there - assertArrayEquals(body, + assertArrayEquals(StringUtils.toUtf8(body), blogManager1.getPostBody(p.getMessage().getId())); stopLifecycles(); @@ -217,10 +223,10 @@ public class BlogManagerTest { defaultInit(); // add a post to blog1 - final byte[] body = TestUtils.getRandomBytes(42); + final String body = TestUtils.getRandomString(42); BlogPost p = blogPostFactory - .createBlogPost(blog1.getId(), null, clock.currentTimeMillis(), - null, author0, CONTENT_TYPE, body); + .createBlogPost(blog1.getId(), clock.currentTimeMillis(), null, + author0, body); blogManager0.addLocalPost(p); // check that post is now in blog1 @@ -287,6 +293,241 @@ public class BlogManagerTest { stopLifecycles(); } + @Test + public void testBlogComment() throws Exception { + startLifecycles(); + defaultInit(); + + // add a post to blog0 + final String body = TestUtils.getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null, + author0, body); + blogManager0.addLocalPost(p); + + // sync the post over + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + + // make sure post arrived + Collection headers1 = + blogManager1.getPostHeaders(blog0.getId()); + assertEquals(1, headers1.size()); + assertEquals(POST, headers1.iterator().next().getType()); + + // 1 adds a comment to that blog post + String comment = "This is a comment on a blog post!"; + blogManager1 + .addLocalComment(author1, blog1.getId(), comment, + headers1.iterator().next()); + + // sync comment over + sync1To0(); + deliveryWaiter.await(TIMEOUT, 2); + + // make sure comment and wrapped post arrived + Collection headers0 = + blogManager0.getPostHeaders(blog1.getId()); + assertEquals(1, headers0.size()); + assertEquals(COMMENT, headers0.iterator().next().getType()); + BlogCommentHeader h = (BlogCommentHeader) headers0.iterator().next(); + assertEquals(author0, h.getParent().getAuthor()); + + // ensure that body can be retrieved from wrapped post + assertArrayEquals(StringUtils.toUtf8(body), + blogManager0.getPostBody(h.getParentId())); + + // 1 has only their own comment in their blog + headers1 = blogManager1.getPostHeaders(blog1.getId()); + assertEquals(1, headers1.size()); + + stopLifecycles(); + } + + @Test + public void testBlogCommentOnOwnPost() throws Exception { + startLifecycles(); + defaultInit(); + + // add a post to blog0 + final String body = TestUtils.getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null, + author0, body); + blogManager0.addLocalPost(p); + + // get header of own post + Collection headers0 = + blogManager0.getPostHeaders(blog0.getId()); + assertEquals(1, headers0.size()); + BlogPostHeader header = headers0.iterator().next(); + + // add a comment on own post + String comment = "This is a comment on my own blog post!"; + blogManager0 + .addLocalComment(author0, blog0.getId(), comment, header); + + // sync the post and comment over + sync0To1(); + deliveryWaiter.await(TIMEOUT, 2); + + // make sure post arrived + Collection headers1 = + blogManager1.getPostHeaders(blog0.getId()); + assertEquals(2, headers1.size()); + + stopLifecycles(); + } + + @Test + public void testCommentOnComment() throws Exception { + startLifecycles(); + defaultInit(); + + // add a post to blog0 + final String body = TestUtils.getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null, + author0, body); + blogManager0.addLocalPost(p); + + // sync the post over + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + + // make sure post arrived + Collection headers1 = + blogManager1.getPostHeaders(blog0.getId()); + assertEquals(1, headers1.size()); + assertEquals(POST, headers1.iterator().next().getType()); + + // 1 reblogs that blog post + blogManager1 + .addLocalComment(author1, blog1.getId(), null, + headers1.iterator().next()); + + // sync comment over + sync1To0(); + deliveryWaiter.await(TIMEOUT, 2); + + // make sure comment and wrapped post arrived + Collection headers0 = + blogManager0.getPostHeaders(blog1.getId()); + assertEquals(1, headers0.size()); + + // get header of comment + BlogPostHeader cHeader = headers0.iterator().next(); + assertEquals(COMMENT, cHeader.getType()); + + // comment on the comment + String comment = "This is a comment on a reblogged post."; + blogManager0 + .addLocalComment(author0, blog0.getId(), comment, cHeader); + + // sync comment over + sync0To1(); + deliveryWaiter.await(TIMEOUT, 3); + + // check that comment arrived + headers1 = + blogManager1.getPostHeaders(blog0.getId()); + assertEquals(2, headers1.size()); + + // get header of comment + cHeader = null; + for (BlogPostHeader h : headers1) { + if (h.getType() == COMMENT) { + cHeader = h; + } + } + assertTrue(cHeader != null); + + // another comment on the comment + String comment2 = "This is a comment on a comment."; + blogManager1.addLocalComment(author1, blog1.getId(), comment2, cHeader); + + // sync comment over + sync1To0(); + deliveryWaiter.await(TIMEOUT, 4); + + // make sure new comment arrived + headers0 = + blogManager0.getPostHeaders(blog1.getId()); + assertEquals(2, headers0.size()); + boolean satisfied = false; + for (BlogPostHeader h : headers0) { + assertEquals(COMMENT, h.getType()); + BlogCommentHeader c = (BlogCommentHeader) h; + if (c.getComment() != null && c.getComment().equals(comment2)) { + assertEquals(author0, c.getParent().getAuthor()); + assertEquals(WRAPPED_COMMENT, c.getParent().getType()); + assertEquals(comment, + ((BlogCommentHeader) c.getParent()).getComment()); + assertEquals(WRAPPED_COMMENT, + ((BlogCommentHeader) c.getParent()).getParent() + .getType()); + assertEquals(WRAPPED_POST, + ((BlogCommentHeader) ((BlogCommentHeader) c + .getParent()).getParent()).getParent() + .getType()); + satisfied = true; + } + } + assertTrue(satisfied); + + stopLifecycles(); + } + + @Test + public void testCommentOnOwnComment() throws Exception { + startLifecycles(); + defaultInit(); + + // add a post to blog0 + final String body = TestUtils.getRandomString(42); + BlogPost p = blogPostFactory + .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null, + author0, body); + blogManager0.addLocalPost(p); + + // sync the post over + sync0To1(); + deliveryWaiter.await(TIMEOUT, 1); + + // make sure post arrived + Collection headers1 = + blogManager1.getPostHeaders(blog0.getId()); + assertEquals(1, headers1.size()); + assertEquals(POST, headers1.iterator().next().getType()); + + // 1 reblogs that blog post with a comment + String comment = "This is a comment on a post."; + blogManager1 + .addLocalComment(author1, blog1.getId(), comment, + headers1.iterator().next()); + + // get comment from own blog + headers1 = blogManager1.getPostHeaders(blog1.getId()); + assertEquals(1, headers1.size()); + assertEquals(COMMENT, headers1.iterator().next().getType()); + BlogCommentHeader ch = (BlogCommentHeader) headers1.iterator().next(); + assertEquals(comment, ch.getComment()); + + comment = "This is a comment on a post with a comment."; + blogManager1.addLocalComment(author1, blog1.getId(), comment, ch); + + // sync both comments over (2 comments + 1 wrapped post) + sync1To0(); + deliveryWaiter.await(TIMEOUT, 3); + + // make sure both comments arrived + Collection headers0 = + blogManager0.getPostHeaders(blog1.getId()); + assertEquals(2, headers0.size()); + + stopLifecycles(); + } + @After public void tearDown() throws Exception { TestUtils.deleteTestDirectory(testDir); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java index 15552fdb1..9906e511e 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java @@ -212,9 +212,10 @@ public class BlogControllerImpl extends DbControllerImpl } private BlogPostHeader getPostHeader(MessageId m) throws DbException { + if (groupId == null) throw new IllegalStateException(); BlogPostHeader header = headerCache.get(m); if (header == null) { - header = blogManager.getPostHeader(m); + header = blogManager.getPostHeader(groupId, m); headerCache.put(m, header); } return header; diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java index 9c36a73a5..f96deda0b 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java @@ -136,11 +136,8 @@ public class BlogPostFragment extends BaseFragment { ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp())); } - if (post.getTitle() != null) { - ui.title.setText(post.getTitle()); - } else { - ui.title.setVisibility(GONE); - } + // TODO remove #598 + ui.title.setVisibility(GONE); ui.body.setText(StringUtils.fromUtf8(post.getBody())); } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java index c3aa850af..55a439e2e 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java @@ -32,11 +32,6 @@ class BlogPostItem implements Comparable { return groupId; } - @Nullable - public String getTitle() { - return header.getTitle(); - } - public long getTimestamp() { return header.getTimestamp(); } @@ -72,11 +67,6 @@ class BlogPostItem implements Comparable { long aTime = getTimeReceived(), bTime = other.getTimeReceived(); if (aTime > bTime) return -1; if (aTime < bTime) return 1; - // Break ties by post title - if (getTitle() != null && other.getTitle() != null) { - return String.CASE_INSENSITIVE_ORDER - .compare(getTitle(), other.getTitle()); - } return 0; } } diff --git a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java index 4e6d24312..dc462331c 100644 --- a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java @@ -2,8 +2,6 @@ package org.briarproject.android.blogs; import android.content.Intent; import android.os.Bundle; -import android.support.design.widget.TextInputEditText; -import android.support.design.widget.TextInputLayout; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; @@ -39,19 +37,16 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static java.util.logging.Level.WARNING; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; -import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; public class WriteBlogPostActivity extends BriarActivity implements OnEditorActionListener { private static final Logger LOG = Logger.getLogger(WriteBlogPostActivity.class.getName()); - private static final String contentType = "text/plain"; @Inject protected AndroidNotificationManager notificationManager; - private TextInputEditText titleInput; private EditText bodyInput; private Button publishButton; private ProgressBar progressBar; @@ -76,16 +71,6 @@ public class WriteBlogPostActivity extends BriarActivity setContentView(R.layout.activity_write_blog_post); - TextInputLayout titleLayout = - (TextInputLayout) findViewById(R.id.titleLayout); - if (titleLayout != null) { - titleLayout.setCounterMaxLength(MAX_BLOG_POST_TITLE_LENGTH); - } - titleInput = (TextInputEditText) findViewById(R.id.titleInput); - if (titleInput != null) { - titleInput.setOnEditorActionListener(this); - } - bodyInput = (EditText) findViewById(R.id.bodyInput); bodyInput.addTextChangedListener(new TextWatcher() { @Override @@ -152,30 +137,24 @@ public class WriteBlogPostActivity extends BriarActivity private void enableOrDisablePublishButton() { int bodyLength = StringUtils.toUtf8(bodyInput.getText().toString()).length; - if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH && - titleInput.getText().length() <= MAX_BLOG_POST_TITLE_LENGTH) + if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH) publishButton.setEnabled(true); else publishButton.setEnabled(false); } private void publish() { - // title - String title = titleInput.getText().toString(); - if (title.length() > MAX_BLOG_POST_TITLE_LENGTH) return; - if (title.length() == 0) title = null; - // body - byte[] body = StringUtils.toUtf8(bodyInput.getText().toString()); + String body = bodyInput.getText().toString(); // hide publish button, show progress bar publishButton.setVisibility(GONE); progressBar.setVisibility(VISIBLE); - storePost(title, body); + storePost(body); } - private void storePost(final String title, final byte[] body) { + private void storePost(final String body) { runOnDbThread(new Runnable() { @Override public void run() { @@ -185,8 +164,7 @@ public class WriteBlogPostActivity extends BriarActivity identityManager.getLocalAuthors(); LocalAuthor author = authors.iterator().next(); BlogPost p = blogPostFactory - .createBlogPost(groupId, title, now, null, author, - contentType, body); + .createBlogPost(groupId, now, null, author, body); blogManager.addLocalPost(p); postPublished(); } catch (DbException | GeneralSecurityException | FormatException e) { diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java index 638074ca1..2ec3bc09b 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java @@ -404,7 +404,7 @@ public class ForumControllerImpl extends DbControllerImpl ForumPostHeader h = new ForumPostHeader(p.getMessage().getId(), p.getParent(), p.getMessage().getTimestamp(), p.getAuthor(), VERIFIED, - p.getContentType(), false); + false); addNewPost(h); } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java new file mode 100644 index 000000000..9ed79b910 --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java @@ -0,0 +1,43 @@ +package org.briarproject.api.blogs; + +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static org.briarproject.api.blogs.MessageType.COMMENT; +import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; + +public class BlogCommentHeader extends BlogPostHeader { + + private String comment; + private BlogPostHeader parent; + + public BlogCommentHeader(@NotNull MessageType type, + @NotNull GroupId groupId, @Nullable String comment, + @NotNull BlogPostHeader parent, @NotNull MessageId id, + long timestamp, long timeReceived, @NotNull Author author, + @NotNull Status authorStatus, boolean read) { + + super(type, groupId, id, parent.getId(), timestamp, + timeReceived, author, authorStatus, read); + + if (type != COMMENT && type != WRAPPED_COMMENT) + throw new IllegalArgumentException("Incompatible Message Type"); + + this.comment = comment; + this.parent = parent; + } + + @Nullable + public String getComment() { + return comment; + } + + @NotNull + public BlogPostHeader getParent() { + return parent; + } +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java index 5a9bff7f4..30d46fa76 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java @@ -10,12 +10,6 @@ public interface BlogConstants { /** The length of a blogs's description in UTF-8 bytes. */ int MAX_BLOG_DESC_LENGTH = 240; - /** The maximum length of a blog post's content type in UTF-8 bytes. */ - int MAX_CONTENT_TYPE_LENGTH = 50; - - /** The length of a blog post's title in UTF-8 bytes. */ - int MAX_BLOG_POST_TITLE_LENGTH = 100; - /** The maximum length of a blog post's body in bytes. */ int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; @@ -31,17 +25,16 @@ public interface BlogConstants { // Metadata keys String KEY_TYPE = "type"; String KEY_DESCRIPTION = "description"; - String KEY_TITLE = "title"; String KEY_TIMESTAMP = "timestamp"; String KEY_TIME_RECEIVED = "timeReceived"; String KEY_AUTHOR_ID = "id"; String KEY_AUTHOR_NAME = "name"; String KEY_PUBLIC_KEY = "publicKey"; String KEY_AUTHOR = "author"; - String KEY_CONTENT_TYPE = "contentType"; String KEY_READ = "read"; String KEY_COMMENT = "comment"; String KEY_ORIGINAL_MSG_ID = "originalMessageId"; - String KEY_CURRENT_MSG_ID = "currentMessageId"; + String KEY_ORIGINAL_PARENT_MSG_ID = "originalParentMessageId"; + String KEY_WRAPPED_MSG_ID = "wrappedMessageId"; } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java index 1d4f0eaee..ac8ea516b 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogManager.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java @@ -7,6 +7,7 @@ import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -31,6 +32,11 @@ public interface BlogManager { /** Stores a local blog post. */ void addLocalPost(Transaction txn, BlogPost p) throws DbException; + /** Add a comment to an existing blog post or reblog it. */ + void addLocalComment(LocalAuthor author, GroupId groupId, + @Nullable String comment, BlogPostHeader wHeader) + throws DbException; + /** Returns the blog with the given ID. */ Blog getBlog(GroupId g) throws DbException; @@ -47,7 +53,7 @@ public interface BlogManager { Collection getBlogs() throws DbException; /** Returns the header of the blog post with the given ID. */ - BlogPostHeader getPostHeader(MessageId m) throws DbException; + BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException; /** Returns the body of the blog post with the given ID. */ byte[] getPostBody(MessageId m) throws DbException; diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPost.java b/briar-api/src/org/briarproject/api/blogs/BlogPost.java index 3854c0472..73c787b29 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPost.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPost.java @@ -9,19 +9,8 @@ import org.jetbrains.annotations.Nullable; public class BlogPost extends ForumPost { - @Nullable - private final String title; - - public BlogPost(@Nullable String title, @NotNull Message message, - @Nullable MessageId parent, @NotNull Author author, - @NotNull String contentType) { - super(message, parent, author, contentType); - - this.title = title; - } - - @Nullable - public String getTitle() { - return title; + public BlogPost(@NotNull Message message, @Nullable MessageId parent, + @NotNull Author author) { + super(message, parent, author); } } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java index 6ced9ba20..46907cadc 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java @@ -1,8 +1,10 @@ package org.briarproject.api.blogs; import org.briarproject.api.FormatException; +import org.briarproject.api.data.BdfList; import org.briarproject.api.identity.LocalAuthor; 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; @@ -11,9 +13,30 @@ import java.security.GeneralSecurityException; public interface BlogPostFactory { - BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title, - long timestamp, @Nullable MessageId parent, - @NotNull LocalAuthor author, @NotNull String contentType, - @NotNull byte[] body) + BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp, + @Nullable MessageId parent, @NotNull LocalAuthor author, + @NotNull String body) throws FormatException, GeneralSecurityException; + + Message createBlogComment(GroupId groupId, LocalAuthor author, + @Nullable String comment, MessageId originalId, MessageId wrappedId) + throws FormatException, GeneralSecurityException; + + /** Wraps a blog post */ + Message createWrappedPost(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body) + throws FormatException; + + /** Re-wraps a previously wrapped post */ + Message createWrappedPost(GroupId groupId, BdfList body) + throws FormatException; + + /** Wraps a blog comment */ + Message createWrappedComment(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body, MessageId currentId) + throws FormatException; + + /** Re-wraps a previously wrapped comment */ + Message createWrappedComment(GroupId groupId, BdfList body, + MessageId currentId) throws FormatException; } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java index f9ef7bdfe..dc191078d 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java @@ -3,33 +3,45 @@ package org.briarproject.api.blogs; import org.briarproject.api.clients.PostHeader; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class BlogPostHeader extends PostHeader { - @Nullable - private final String title; + private final MessageType type; + private final GroupId groupId; private final long timeReceived; - public BlogPostHeader(@Nullable String title, @NotNull MessageId id, - long timestamp, long timeReceived, @NotNull Author author, - @NotNull Status authorStatus, @NotNull String contentType, - boolean read) { - super(id, null, timestamp, author, authorStatus, contentType, read); + public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId, + @NotNull MessageId id, @Nullable MessageId parentId, long timestamp, + long timeReceived, @NotNull Author author, + @NotNull Status authorStatus, boolean read) { + super(id, parentId, timestamp, author, authorStatus, read); - this.title = title; + this.type = type; + this.groupId = groupId; this.timeReceived = timeReceived; } - @Nullable - public String getTitle() { - return title; + public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId, + @NotNull MessageId id, long timestamp, long timeReceived, + @NotNull Author author, @NotNull Status authorStatus, + boolean read) { + this(type, groupId, id, null, timestamp, timeReceived, author, + authorStatus, read); + } + + public MessageType getType() { + return type; + } + + public GroupId getGroupId() { + return groupId; } public long getTimeReceived() { return timeReceived; } - } diff --git a/briar-api/src/org/briarproject/api/clients/ClientHelper.java b/briar-api/src/org/briarproject/api/clients/ClientHelper.java index d4625af66..c6553c91b 100644 --- a/briar-api/src/org/briarproject/api/clients/ClientHelper.java +++ b/briar-api/src/org/briarproject/api/clients/ClientHelper.java @@ -10,6 +10,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; +import java.security.GeneralSecurityException; import java.util.Map; public interface ClientHelper { @@ -83,4 +84,9 @@ public interface ClientHelper { throws FormatException; BdfList toList(byte[] b, int off, int len) throws FormatException; + + BdfList toList(byte[] b) throws FormatException; + + byte[] sign(BdfList toSign, byte[] privateKey) + throws FormatException, GeneralSecurityException; } diff --git a/briar-api/src/org/briarproject/api/clients/PostHeader.java b/briar-api/src/org/briarproject/api/clients/PostHeader.java index 33f0d2877..7ffed6364 100644 --- a/briar-api/src/org/briarproject/api/clients/PostHeader.java +++ b/briar-api/src/org/briarproject/api/clients/PostHeader.java @@ -1,6 +1,7 @@ package org.briarproject.api.clients; import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.Author.Status; import org.briarproject.api.sync.MessageId; public abstract class PostHeader { @@ -9,19 +10,16 @@ public abstract class PostHeader { private final MessageId parentId; private final long timestamp; private final Author author; - private final Author.Status authorStatus; - private final String contentType; + private final Status authorStatus; private final boolean read; public PostHeader(MessageId id, MessageId parentId, long timestamp, - Author author, Author.Status authorStatus, String contentType, - boolean read) { + Author author, Status authorStatus, boolean read) { this.id = id; this.parentId = parentId; this.timestamp = timestamp; this.author = author; this.authorStatus = authorStatus; - this.contentType = contentType; this.read = read; } @@ -33,14 +31,10 @@ public abstract class PostHeader { return author; } - public Author.Status getAuthorStatus() { + public Status getAuthorStatus() { return authorStatus; } - public String getContentType() { - return contentType; - } - public long getTimestamp() { return timestamp; } diff --git a/briar-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java index 9523b20cc..97557d5fe 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumConstants.java +++ b/briar-api/src/org/briarproject/api/forum/ForumConstants.java @@ -27,7 +27,6 @@ public interface ForumConstants { String KEY_NAME = "name"; String KEY_PUBLIC_NAME = "publicKey"; String KEY_AUTHOR = "author"; - String KEY_CONTENT_TYPE = "contentType"; String KEY_LOCAL = "local"; String KEY_READ = "read"; diff --git a/briar-api/src/org/briarproject/api/forum/ForumPost.java b/briar-api/src/org/briarproject/api/forum/ForumPost.java index 404d6c520..6e433137b 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumPost.java +++ b/briar-api/src/org/briarproject/api/forum/ForumPost.java @@ -9,14 +9,11 @@ public class ForumPost { private final Message message; private final MessageId parent; private final Author author; - private final String contentType; - public ForumPost(Message message, MessageId parent, Author author, - String contentType) { + public ForumPost(Message message, MessageId parent, Author author) { this.message = message; this.parent = parent; this.author = author; - this.contentType = contentType; } public Message getMessage() { @@ -30,8 +27,4 @@ public class ForumPost { public Author getAuthor() { return author; } - - public String getContentType() { - return contentType; - } } diff --git a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java index c25a9e251..ef08dc022 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java +++ b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java @@ -9,9 +9,8 @@ public class ForumPostHeader extends PostHeader implements MessageTree.MessageNode { public ForumPostHeader(MessageId id, MessageId parentId, long timestamp, - Author author, Author.Status authorStatus, String contentType, - boolean read) { - super(id, parentId, timestamp, author, authorStatus, contentType, read); + Author author, Author.Status authorStatus, boolean read) { + super(id, parentId, timestamp, author, authorStatus, read); } } diff --git a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java index 82f7d7ab2..7853ec403 100644 --- a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java @@ -59,7 +59,7 @@ class BlogFactoryImpl implements BlogFactory { byte[] descriptor = g.getDescriptor(); // Blog Name, Author Name, Public Key - BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length); + BdfList blog = clientHelper.toList(descriptor); String name = blog.getString(0); Author a = authorFactory.createAuthor(blog.getString(1), blog.getRaw(2)); diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index 097e594f4..fc278c556 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -2,10 +2,13 @@ package org.briarproject.blogs; import org.briarproject.api.FormatException; import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogCommentHeader; import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogPost; +import org.briarproject.api.blogs.BlogPostFactory; import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.blogs.MessageType; import org.briarproject.api.clients.Client; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.Contact; @@ -33,10 +36,14 @@ 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.Nullable; +import java.security.GeneralSecurityException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -45,19 +52,26 @@ import java.util.logging.Logger; import javax.inject.Inject; -import static java.util.logging.Level.WARNING; 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_CONTENT_TYPE; +import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; +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_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; -import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE; +import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; +import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_MSG_ID; +import static org.briarproject.api.blogs.MessageType.COMMENT; +import static org.briarproject.api.blogs.MessageType.POST; +import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; +import static org.briarproject.api.blogs.MessageType.WRAPPED_POST; import static org.briarproject.api.contact.ContactManager.AddContactHook; import static org.briarproject.api.contact.ContactManager.RemoveContactHook; +import static org.briarproject.blogs.BlogPostValidator.authorToBdfDictionary; class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, AddContactHook, RemoveContactHook, Client, @@ -74,18 +88,21 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, private final IdentityManager identityManager; private final ContactManager contactManager; private final BlogFactory blogFactory; + private final BlogPostFactory blogPostFactory; private final List removeHooks; @Inject BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager, ClientHelper clientHelper, MetadataParser metadataParser, - ContactManager contactManager, BlogFactory blogFactory) { + ContactManager contactManager, BlogFactory blogFactory, + BlogPostFactory blogPostFactory) { super(clientHelper, metadataParser); this.db = db; this.identityManager = identityManager; this.contactManager = contactManager; this.blogFactory = blogFactory; + this.blogPostFactory = blogPostFactory; removeHooks = new CopyOnWriteArrayList(); } @@ -160,13 +177,43 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, protected void incomingMessage(Transaction txn, Message m, BdfList list, BdfDictionary meta) throws DbException, FormatException { - clientHelper.setMessageShared(txn, m, true); - GroupId groupId = m.getGroupId(); - BlogPostHeader h = getPostHeaderFromMetadata(txn, m.getId(), meta); - BlogPostAddedEvent event = - new BlogPostAddedEvent(groupId, h, false); - txn.attach(event); + MessageType type = getMessageType(meta); + + if (type == POST || type == COMMENT) { + BlogPostHeader h = + getPostHeaderFromMetadata(txn, groupId, m.getId(), meta); + + // check that original message IDs match + if (type == COMMENT) { + BdfDictionary d = clientHelper + .getMessageMetadataAsDictionary(txn, h.getParentId()); + byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID); + byte[] original2 = meta.getRaw(KEY_ORIGINAL_MSG_ID); + if (!Arrays.equals(original1, original2)) { + throw new FormatException(); + } + } + // share dependencies recursively + share(txn, h); + + // broadcast event about new post or comment + BlogPostAddedEvent event = + new BlogPostAddedEvent(groupId, h, false); + txn.attach(event); + } else if (type == WRAPPED_COMMENT) { + // Check that the original message ID in the dependency's metadata + // matches the original parent ID of the wrapped comment + MessageId dependencyId = + new MessageId(meta.getRaw(KEY_WRAPPED_MSG_ID)); + BdfDictionary d = clientHelper + .getMessageMetadataAsDictionary(txn, dependencyId); + byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID); + byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID); + if (!Arrays.equals(original1, original2)) { + throw new FormatException(); + } + } } @Override @@ -247,17 +294,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, public void addLocalPost(Transaction txn, BlogPost p) throws DbException { try { BdfDictionary meta = new BdfDictionary(); - if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle()); + meta.put(KEY_TYPE, POST.getInt()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); - - Author a = p.getAuthor(); - BdfDictionary authorMeta = new BdfDictionary(); - authorMeta.put(KEY_AUTHOR_ID, a.getId()); - authorMeta.put(KEY_AUTHOR_NAME, a.getName()); - authorMeta.put(KEY_PUBLIC_KEY, a.getPublicKey()); - meta.put(KEY_AUTHOR, authorMeta); - - meta.put(KEY_CONTENT_TYPE, p.getContentType()); + meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor())); meta.put(KEY_READ, true); clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true); @@ -265,7 +304,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, // broadcast event about new post GroupId groupId = p.getMessage().getGroupId(); MessageId postId = p.getMessage().getId(); - BlogPostHeader h = getPostHeaderFromMetadata(txn, postId, meta); + BlogPostHeader h = + getPostHeaderFromMetadata(txn, 0, groupId, postId, meta); BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); txn.attach(event); @@ -274,6 +314,126 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } + @Override + public void addLocalComment(LocalAuthor author, GroupId groupId, + @Nullable String comment, BlogPostHeader wHeader) + throws DbException { + + if (wHeader.getType() != POST && wHeader.getType() != COMMENT) + throw new IllegalArgumentException("Comment on unknown type!"); + + Transaction txn = db.startTransaction(false); + try { + // Wrap post that we are commenting on + MessageId wMessageId = wrapMessage(txn, 0, groupId, wHeader); + + // Get ID of original message + MessageId originalId = wHeader.getId(); + if (wHeader.getType() != POST && wMessageId.equals(originalId)) { + // we comment on a non-post message that needs no wrapping, + // so get original message ID from it + BdfDictionary d = clientHelper + .getMessageMetadataAsDictionary(txn, wMessageId); + originalId = new MessageId(d.getRaw(KEY_ORIGINAL_MSG_ID)); + } + + // Create actual comment + Message message = blogPostFactory + .createBlogComment(groupId, author, comment, originalId, + wMessageId); + BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_TYPE, COMMENT.getInt()); + if (comment != null) meta.put(KEY_COMMENT, comment); + meta.put(KEY_TIMESTAMP, message.getTimestamp()); + meta.put(KEY_ORIGINAL_MSG_ID, wHeader.getId()); + meta.put(KEY_WRAPPED_MSG_ID, wMessageId); + meta.put(KEY_AUTHOR, authorToBdfDictionary(author)); + + // Send comment + clientHelper.addLocalMessage(txn, message, CLIENT_ID, meta, true); + + // broadcast event + BlogPostHeader h = + getPostHeaderFromMetadata(txn, groupId, message.getId(), + meta); + BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); + txn.attach(event); + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); + } catch (GeneralSecurityException e) { + throw new DbException(e); + } finally { + //noinspection ThrowFromFinallyBlock + db.endTransaction(txn); + } + } + + private MessageId wrapMessage(Transaction txn, int level, GroupId groupId, + BlogPostHeader wHeader) + throws DbException, FormatException { + + if (groupId.equals(wHeader.getGroupId())) { + // We are trying to wrap a post that is already in our group. + // This is unnecessary, so just return the post's MessageId + return wHeader.getId(); + } + + // Get Group and Body of Message to be wrapped + Group wGroup = db.getGroup(txn, wHeader.getGroupId()); + BdfList wBody = clientHelper.getMessageAsList(txn, wHeader.getId()); + byte[] wDescriptor = wGroup.getDescriptor(); + long wTimestamp = wHeader.getTimestamp(); + Message wMessage; + + BdfDictionary meta = new BdfDictionary(); + MessageType type = wHeader.getType(); + if (type == POST) { + // Wrap post + wMessage = blogPostFactory + .createWrappedPost(groupId, wDescriptor, wTimestamp, wBody); + meta.put(KEY_TYPE, WRAPPED_POST.getInt()); + } else if (type == COMMENT) { + BlogCommentHeader wComment = (BlogCommentHeader) wHeader; + MessageId wrappedId = + wrapMessage(txn, ++level, groupId, wComment.getParent()); + // Wrap comment + wMessage = blogPostFactory + .createWrappedComment(groupId, wDescriptor, wTimestamp, + wBody, wrappedId); + meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); + if(wComment.getComment() != null) + meta.put(KEY_COMMENT, wComment.getComment()); + meta.put(KEY_WRAPPED_MSG_ID, wrappedId); + } else if (type == WRAPPED_POST) { + // Re-wrap wrapped post without adding another wrapping layer + wMessage = blogPostFactory.createWrappedPost(groupId, wBody); + meta.put(KEY_TYPE, WRAPPED_POST.getInt()); + } else if (type == WRAPPED_COMMENT) { + BlogCommentHeader wComment = (BlogCommentHeader) wHeader; + MessageId wrappedId = + wrapMessage(txn, ++level, groupId, wComment.getParent()); + // Re-wrap wrapped comment + wMessage = blogPostFactory + .createWrappedComment(groupId, wBody, wrappedId); + meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); + if(wComment.getComment() != null) + meta.put(KEY_COMMENT, wComment.getComment()); + meta.put(KEY_WRAPPED_MSG_ID, wrappedId); + } else { + throw new IllegalArgumentException( + "Unknown Message Type: " + type); + } + meta.put(KEY_ORIGINAL_MSG_ID, wHeader.getId()); + meta.put(KEY_AUTHOR, authorToBdfDictionary(wHeader.getAuthor())); + meta.put(KEY_TIMESTAMP, wHeader.getTimestamp()); + meta.put(KEY_TIME_RECEIVED, wHeader.getTimeReceived()); + + // Send wrapped message and store metadata + clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true); + return wMessage.getId(); + } + @Override public Blog getBlog(GroupId g) throws DbException { Blog blog; @@ -340,12 +500,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } @Override - public BlogPostHeader getPostHeader(MessageId m) throws DbException { + public BlogPostHeader getPostHeader(GroupId g, MessageId m) + throws DbException { Transaction txn = db.startTransaction(true); try { BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(txn, m); - BlogPostHeader h = getPostHeaderFromMetadata(txn, m, meta); + BlogPostHeader h = getPostHeaderFromMetadata(txn, g, m, meta); txn.setComplete(); return h; } catch (FormatException e) { @@ -366,25 +527,50 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } + // TODO directly return String (#598) private byte[] getPostBody(BdfList message) throws FormatException { - // content, signature - // content: parent, contentType, title, body, attachments - BdfList content = message.getList(0); - return content.getRaw(3); + MessageType type = MessageType.valueOf(message.getLong(0).intValue()); + if (type == POST) { + // type, body, signature + return StringUtils.toUtf8(message.getString(1)); + } else if (type == WRAPPED_POST) { + // type, p_group descriptor, p_timestamp, p_content, p_signature + return StringUtils.toUtf8(message.getString(3)); + } else { + throw new FormatException(); + } } @Override public Collection getPostHeaders(GroupId g) throws DbException { + // Query for posts and comments only + BdfDictionary query1 = BdfDictionary.of( + new BdfEntry(KEY_TYPE, POST.getInt()) + ); + BdfDictionary query2 = BdfDictionary.of( + new BdfEntry(KEY_TYPE, COMMENT.getInt()) + ); + + // TODO this could be optimized to re-use existing headers + + Collection headers = new ArrayList(); Transaction txn = db.startTransaction(true); try { + Map metadata1 = + clientHelper.getMessageMetadataAsDictionary(txn, g, query1); + Map metadata2 = + clientHelper.getMessageMetadataAsDictionary(txn, g, query2); Map metadata = - clientHelper.getMessageMetadataAsDictionary(txn, g); - List headers = new ArrayList(); + new HashMap( + metadata1.size() + metadata2.size()); + metadata.putAll(metadata1); + metadata.putAll(metadata2); for (Entry entry : metadata.entrySet()) { - BlogPostHeader h = getPostHeaderFromMetadata(txn, - entry.getKey(), entry.getValue()); + BdfDictionary meta = entry.getValue(); + BlogPostHeader h = + getPostHeaderFromMetadata(txn, g, entry.getKey(), meta); headers.add(h); } txn.setComplete(); @@ -419,10 +605,24 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, - MessageId id, BdfDictionary meta) + GroupId groupId, MessageId id, BdfDictionary meta) + throws DbException, FormatException { + return getPostHeaderFromMetadata(txn, 0, groupId, id, meta); + } + + private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, int level, + GroupId groupId, MessageId id) throws DbException, FormatException { + BdfDictionary meta = + clientHelper.getMessageMetadataAsDictionary(txn, id); + return getPostHeaderFromMetadata(txn, level, groupId, id, meta); + } + + private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, int level, + GroupId groupId, MessageId id, BdfDictionary meta) throws DbException, FormatException { - String title = meta.getOptionalString(KEY_TITLE); + MessageType type = getMessageType(meta); + long timestamp = meta.getLong(KEY_TIMESTAMP); long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp); @@ -431,12 +631,33 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, String name = d.getString(KEY_AUTHOR_NAME); byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY); Author author = new Author(authorId, name, publicKey); - Status authorStatus; - authorStatus = identityManager.getAuthorStatus(txn, authorId); + Status authorStatus = identityManager.getAuthorStatus(txn, authorId); - String contentType = meta.getString(KEY_CONTENT_TYPE); - boolean read = meta.getBoolean(KEY_READ); - return new BlogPostHeader(title, id, timestamp, timeReceived, author, - authorStatus, contentType, read); + boolean read = meta.getBoolean(KEY_READ, false); + + if (type == COMMENT || type == WRAPPED_COMMENT) { + String comment = meta.getOptionalString(KEY_COMMENT); + MessageId parentId = new MessageId(meta.getRaw(KEY_WRAPPED_MSG_ID)); + BlogPostHeader parent = + getPostHeaderFromMetadata(txn, ++level, groupId, parentId); + return new BlogCommentHeader(type, groupId, comment, parent, id, + timestamp, timeReceived, author, authorStatus, read); + } else { + return new BlogPostHeader(type, groupId, id, timestamp, + timeReceived, author, authorStatus, read); + } + } + + private MessageType getMessageType(BdfDictionary d) throws FormatException { + Long longType = d.getLong(KEY_TYPE); + return MessageType.valueOf(longType.intValue()); + } + + private void share(Transaction txn, BlogPostHeader h) throws DbException { + clientHelper.setMessageShared(txn, h.getId(), true); + if (h instanceof BlogCommentHeader) { + BlogPostHeader h2 = ((BlogCommentHeader) h).getParent(); + share(txn, h2); + } } } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java index ce134d33c..5d55c2ddb 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -4,16 +4,12 @@ import org.briarproject.api.FormatException; import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPostFactory; 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.Signature; import org.briarproject.api.data.BdfList; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; -import org.briarproject.util.StringUtils; +import org.briarproject.api.system.Clock; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,52 +18,127 @@ import java.security.GeneralSecurityException; import javax.inject.Inject; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; -import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; -import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH; +import static org.briarproject.api.blogs.MessageType.COMMENT; +import static org.briarproject.api.blogs.MessageType.POST; +import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; +import static org.briarproject.api.blogs.MessageType.WRAPPED_POST; class BlogPostFactoryImpl implements BlogPostFactory { - private final CryptoComponent crypto; private final ClientHelper clientHelper; + private final Clock clock; @Inject - BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) { - this.crypto = crypto; + BlogPostFactoryImpl(ClientHelper clientHelper, Clock clock) { this.clientHelper = clientHelper; + this.clock = clock; } @Override - public BlogPost createBlogPost(@NotNull GroupId groupId, - @Nullable String title, long timestamp, - @Nullable MessageId parent, @NotNull LocalAuthor author, - @NotNull String contentType, @NotNull byte[] body) + public BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp, + @Nullable MessageId parent, @NotNull LocalAuthor author, + @NotNull String body) throws FormatException, GeneralSecurityException { // Validate the arguments - if (title != null && - StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH) - throw new IllegalArgumentException(); - if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH) - throw new IllegalArgumentException(); - if (body.length > MAX_BLOG_POST_BODY_LENGTH) + if (body.length() > MAX_BLOG_POST_BODY_LENGTH) throw new IllegalArgumentException(); // Serialise the data to be signed - BdfList content = BdfList.of(parent, contentType, title, body, null); - BdfList signed = BdfList.of(groupId, timestamp, content); + BdfList signed = BdfList.of(groupId, timestamp, body); // Generate the signature - Signature signature = crypto.getSignature(); - KeyParser keyParser = crypto.getSignatureKeyParser(); - PrivateKey privateKey = - keyParser.parsePrivateKey(author.getPrivateKey()); - signature.initSign(privateKey); - signature.update(clientHelper.toByteArray(signed)); - byte[] sig = signature.sign(); + byte[] sig = clientHelper.sign(signed, author.getPrivateKey()); // Serialise the signed message - BdfList message = BdfList.of(content, sig); + BdfList message = BdfList.of(POST.getInt(), body, sig); Message m = clientHelper.createMessage(groupId, timestamp, message); - return new BlogPost(title, m, parent, author, contentType); + return new BlogPost(m, parent, author); } + + @Override + public Message createBlogComment(GroupId groupId, LocalAuthor author, + @Nullable String comment, MessageId originalId, MessageId wrappedId) + throws FormatException, GeneralSecurityException { + + long timestamp = clock.currentTimeMillis(); + + // Generate the signature + BdfList signed = + BdfList.of(groupId, timestamp, comment, originalId, wrappedId); + byte[] sig = clientHelper.sign(signed, author.getPrivateKey()); + + // Serialise the signed message + BdfList message = + BdfList.of(COMMENT.getInt(), comment, originalId, wrappedId, + sig); + return clientHelper.createMessage(groupId, timestamp, message); + } + + @Override + public Message createWrappedPost(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body) + throws FormatException { + + // Serialise the message + String content = body.getString(1); + byte[] signature = body.getRaw(2); + BdfList message = + BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp, + content, signature); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + + @Override + public Message createWrappedPost(GroupId groupId, BdfList body) + throws FormatException { + + // Serialise the message + byte[] descriptor = body.getRaw(1); + long timestamp = body.getLong(2); + String content = body.getString(3); + byte[] signature = body.getRaw(4); + BdfList message = + BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp, + content, signature); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + + @Override + public Message createWrappedComment(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body, MessageId currentId) + throws FormatException { + + // Serialise the message + String comment = body.getOptionalString(1); + byte[] originalId = body.getRaw(2); + byte[] oldId = body.getRaw(3); + byte[] signature = body.getRaw(4); + BdfList message = + BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, + comment, originalId, oldId, signature, currentId); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + + @Override + public Message createWrappedComment(GroupId groupId, BdfList body, + MessageId currentId) throws FormatException { + + // Serialise the message + byte[] descriptor = body.getRaw(1); + long timestamp = body.getLong(2); + String comment = body.getOptionalString(3); + byte[] originalId = body.getRaw(4); + byte[] oldId = body.getRaw(5); + byte[] signature = body.getRaw(6); + BdfList message = + BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, + comment, originalId, oldId, signature, currentId); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java index 15b74adbe..9014e76ba 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -1,7 +1,6 @@ package org.briarproject.blogs; import org.briarproject.api.FormatException; -import org.briarproject.api.UniqueId; import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.MessageType; @@ -33,18 +32,17 @@ 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_CONTENT_TYPE; -import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID; +import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID; +import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_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.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; -import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; -import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; -import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH; +import static org.briarproject.api.blogs.MessageType.COMMENT; +import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; class BlogPostValidator extends BdfMessageValidator { @@ -72,13 +70,6 @@ class BlogPostValidator extends BdfMessageValidator { BdfMessageContext c; - // TODO Remove! For Temporary Backwards Compatibility only! - if (body.get(0) instanceof BdfList) { - c = validatePost(m, g, body); - addMessageMetadata(c, m.getTimestamp()); - return c; - } - int type = body.getLong(0).intValue(); body.removeElementAt(0); switch (MessageType.valueOf(type)) { @@ -108,54 +99,33 @@ class BlogPostValidator extends BdfMessageValidator { // Content, Signature checkSize(body, 2); - BdfList content = body.getList(0); - - // Content: content type, title (optional), post body, - // attachments (optional) - checkSize(content, 5); - // Parent ID is optional - // TODO remove when breaking backwards compatibility - byte[] parent = content.getOptionalRaw(0); - checkLength(parent, UniqueId.LENGTH); - // Content type - String contentType = content.getString(1); - checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH); - if (!contentType.equals("text/plain")) - throw new InvalidMessageException("Invalid content type"); - // Blog post title is optional - String title = content.getOptionalString(2); - checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH); - // Blog post body - byte[] postBody = content.getRaw(3); + String postBody = body.getString(0); checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH); - // Attachments - content.getOptionalDictionary(4); // Verify Signature byte[] sig = body.getRaw(1); checkLength(sig, 1, MAX_SIGNATURE_LENGTH); - BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content); + 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); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - if (title != null) meta.put(KEY_TITLE, title); + meta.put(KEY_ORIGINAL_MSG_ID, m.getId()); meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); - meta.put(KEY_CONTENT_TYPE, contentType); return new BdfMessageContext(meta, null); } private BdfMessageContext validateComment(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { - // comment, parent_original_id, signature, parent_current_id + // comment, parent_original_id, parent_id, signature checkSize(body, 4); // Comment String comment = body.getOptionalString(0); - checkLength(comment, 0, MAX_BLOG_POST_BODY_LENGTH); + checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH); // parent_original_id // The ID of a post or comment in this group or another group @@ -163,28 +133,29 @@ class BlogPostValidator extends BdfMessageValidator { checkLength(originalIdBytes, MessageId.LENGTH); MessageId originalId = new MessageId(originalIdBytes); - // Signature - byte[] sig = body.getRaw(2); - checkLength(sig, 0, MAX_SIGNATURE_LENGTH); - BdfList signed = - BdfList.of(g.getId(), m.getTimestamp(), comment, originalId); - Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter - Author a = b.getAuthor(); - verifySignature(sig, a.getPublicKey(), signed); - - // parent_current_id + // parent_id // The ID of a post, comment, wrapped post or wrapped comment in this // group, which had the ID parent_original_id in the group // where it was originally posted - byte[] currentIdBytes = body.getRaw(3); + byte[] currentIdBytes = body.getRaw(2); checkLength(currentIdBytes, MessageId.LENGTH); MessageId currentId = new MessageId(currentIdBytes); + // Signature + byte[] sig = body.getRaw(3); + checkLength(sig, 0, MAX_SIGNATURE_LENGTH); + BdfList signed = + BdfList.of(g.getId(), m.getTimestamp(), comment, originalId, + currentId); + Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter + Author a = b.getAuthor(); + verifySignature(sig, a.getPublicKey(), signed); + // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); if (comment != null) meta.put(KEY_COMMENT, comment); meta.put(KEY_ORIGINAL_MSG_ID, originalId); - meta.put(KEY_CURRENT_MSG_ID, currentId); + meta.put(KEY_WRAPPED_MSG_ID, currentId); meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); Collection dependencies = Collections.singleton(currentId); return new BdfMessageContext(meta, dependencies); @@ -193,7 +164,7 @@ class BlogPostValidator extends BdfMessageValidator { private BdfMessageContext validateWrappedPost(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { - // group descriptor, timestamp, content, signature + // p_group descriptor, p_timestamp, p_content, p_signature checkSize(body, 4); // Group Descriptor @@ -201,9 +172,10 @@ class BlogPostValidator extends BdfMessageValidator { // Timestamp of Wrapped Post long wTimestamp = body.getLong(1); + if (wTimestamp < 0) throw new FormatException(); // Content of Wrapped Post - BdfList content = body.getList(2); + String content = body.getString(2); // Signature of Wrapped Post byte[] signature = body.getRaw(3); @@ -212,71 +184,81 @@ class BlogPostValidator extends BdfMessageValidator { // Get and Validate the Wrapped Message Group wGroup = groupFactory .createGroup(BlogManagerImpl.CLIENT_ID, descriptor); - BdfList wBodyList = BdfList.of(content, signature); + BdfList wBodyList = BdfList.of(POST.getInt(), content, signature); byte[] wBody = clientHelper.toByteArray(wBodyList); Message wMessage = messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody); + wBodyList.remove(0); BdfMessageContext c = validatePost(wMessage, wGroup, wBodyList); // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); + meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId()); meta.put(KEY_TIMESTAMP, wTimestamp); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); - meta.put(KEY_CONTENT_TYPE, - c.getDictionary().getString(KEY_CONTENT_TYPE)); return new BdfMessageContext(meta, null); } private BdfMessageContext validateWrappedComment(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { - // group descriptor, timestamp, comment, parent_original_id, signature, - // parent_current_id - checkSize(body, 6); + // c_group descriptor, c_timestamp, c_comment, c_parent_original_id, + // c_parent_id, c_signature, parent_id + checkSize(body, 7); // Group Descriptor byte[] descriptor = body.getRaw(0); // Timestamp of Wrapped Comment long wTimestamp = body.getLong(1); + if (wTimestamp < 0) throw new FormatException(); // Body of Wrapped Comment String comment = body.getOptionalString(2); + checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH); - // parent_original_id + // c_parent_original_id // Taken from the original comment byte[] originalIdBytes = body.getRaw(3); checkLength(originalIdBytes, MessageId.LENGTH); MessageId originalId = new MessageId(originalIdBytes); - // signature + // c_parent_id // Taken from the original comment - byte[] signature = body.getRaw(4); + byte[] oldIdBytes = body.getRaw(4); + checkLength(oldIdBytes, MessageId.LENGTH); + MessageId oldId = new MessageId(oldIdBytes); + + // c_signature + // Taken from the original comment + byte[] signature = body.getRaw(5); checkLength(signature, 1, MAX_SIGNATURE_LENGTH); - // parent_current_id + // parent_id // The ID of a post, comment, wrapped post or wrapped comment in this - // group, which had the ID parent_original_id in the group + // group, which had the ID c_parent_original_id in the group // where it was originally posted - byte[] currentIdBytes = body.getRaw(5); + byte[] currentIdBytes = body.getRaw(6); checkLength(currentIdBytes, MessageId.LENGTH); MessageId currentId = new MessageId(currentIdBytes); // Get and Validate the Wrapped Comment Group wGroup = groupFactory .createGroup(BlogManagerImpl.CLIENT_ID, descriptor); - BdfList wBodyList = BdfList.of(comment, originalId, signature, - currentId); + BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, originalId, + oldId, signature); byte[] wBody = clientHelper.toByteArray(wBodyList); Message wMessage = messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody); + wBodyList.remove(0); BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList); // Return the metadata and dependencies Collection dependencies = Collections.singleton(currentId); BdfDictionary meta = new BdfDictionary(); meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId()); - meta.put(KEY_CURRENT_MSG_ID, currentId); + meta.put(KEY_ORIGINAL_PARENT_MSG_ID, originalId); + meta.put(KEY_WRAPPED_MSG_ID, currentId); meta.put(KEY_TIMESTAMP, wTimestamp); if (comment != null) meta.put(KEY_COMMENT, comment); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java index a93d39aa4..91cc68c7c 100644 --- a/briar-core/src/org/briarproject/blogs/BlogsModule.java +++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java @@ -50,9 +50,9 @@ public class BlogsModule { } @Provides - BlogPostFactory provideBlogPostFactory(CryptoComponent crypto, - ClientHelper clientHelper) { - return new BlogPostFactoryImpl(crypto, clientHelper); + BlogPostFactory provideBlogPostFactory(ClientHelper clientHelper, + Clock clock) { + return new BlogPostFactoryImpl(clientHelper, clock); } @Provides diff --git a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java index 5612e06b8..a8af6137a 100644 --- a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java +++ b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java @@ -2,6 +2,10 @@ package org.briarproject.clients; import org.briarproject.api.FormatException; 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.Signature; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfReader; @@ -23,6 +27,7 @@ import org.briarproject.api.sync.MessageId; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -40,18 +45,20 @@ class ClientHelperImpl implements ClientHelper { private final BdfWriterFactory bdfWriterFactory; private final MetadataParser metadataParser; private final MetadataEncoder metadataEncoder; + private final CryptoComponent cryptoComponent; @Inject ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser, - MetadataEncoder metadataEncoder) { + MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) { this.db = db; this.messageFactory = messageFactory; this.bdfReaderFactory = bdfReaderFactory; this.bdfWriterFactory = bdfWriterFactory; this.metadataParser = metadataParser; this.metadataEncoder = metadataEncoder; + this.cryptoComponent = cryptoComponent; } @Override @@ -303,4 +310,21 @@ class ClientHelperImpl implements ClientHelper { throw new RuntimeException(e); } } + + @Override + public BdfList toList(byte[] b) throws FormatException { + return toList(b, 0, b.length); + } + + @Override + public byte[] sign(BdfList toSign, byte[] privateKey) + throws FormatException, GeneralSecurityException { + Signature signature = cryptoComponent.getSignature(); + KeyParser keyParser = cryptoComponent.getSignatureKeyParser(); + PrivateKey key = + keyParser.parsePrivateKey(privateKey); + signature.initSign(key); + signature.update(toByteArray(toSign)); + return signature.sign(); + } } diff --git a/briar-core/src/org/briarproject/clients/ClientsModule.java b/briar-core/src/org/briarproject/clients/ClientsModule.java index 302939ff3..1262632c0 100644 --- a/briar-core/src/org/briarproject/clients/ClientsModule.java +++ b/briar-core/src/org/briarproject/clients/ClientsModule.java @@ -26,9 +26,10 @@ public class ClientsModule { ClientHelper provideClientHelper(DatabaseComponent db, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser, - MetadataEncoder metadataEncoder) { + MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) { return new ClientHelperImpl(db, messageFactory, bdfReaderFactory, - bdfWriterFactory, metadataParser, metadataEncoder); + bdfWriterFactory, metadataParser, metadataEncoder, + cryptoComponent); } @Provides diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java index 9379840da..344165b32 100644 --- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java +++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java @@ -23,10 +23,10 @@ import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.feed.Feed; import org.briarproject.api.feed.FeedManager; -import org.briarproject.api.lifecycle.IoExecutor; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; +import org.briarproject.api.lifecycle.IoExecutor; import org.briarproject.api.lifecycle.Service; import org.briarproject.api.lifecycle.ServiceException; import org.briarproject.api.sync.ClientId; @@ -41,7 +41,6 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.security.GeneralSecurityException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -440,15 +439,14 @@ class FeedManagerImpl implements FeedManager, Service, Client { // get other information for post GroupId groupId = feed.getBlogId(); long time = clock.currentTimeMillis(); - byte[] body = getPostBody(b.toString()); + String body = getPostBody(b.toString()); try { // create and store post Blog blog = blogManager.getBlog(txn, groupId); AuthorId authorId = blog.getAuthor().getId(); LocalAuthor author = identityManager.getLocalAuthor(txn, authorId); BlogPost post = blogPostFactory - .createBlogPost(groupId, null, time, null, author, - "text/plain", body); + .createBlogPost(groupId, time, null, author, body); blogManager.addLocalPost(txn, post); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -472,10 +470,9 @@ class FeedManagerImpl implements FeedManager, Service, Client { return StringUtils.trim(s.replaceAll("<(?s).*?>", "")); } - private byte[] getPostBody(String text) { - byte[] body = StringUtils.toUtf8(text); - if (body.length <= MAX_BLOG_POST_BODY_LENGTH) return body; - else return Arrays.copyOfRange(body, 0, MAX_BLOG_POST_BODY_LENGTH - 1); + private String getPostBody(String text) { + if (text.length() <= MAX_BLOG_POST_BODY_LENGTH) return text; + else return text.substring(0, MAX_BLOG_POST_BODY_LENGTH); } /** diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index b33842fc3..3e8b5de64 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -38,7 +38,6 @@ import java.util.logging.Logger; import javax.inject.Inject; import static org.briarproject.api.forum.ForumConstants.KEY_AUTHOR; -import static org.briarproject.api.forum.ForumConstants.KEY_CONTENT_TYPE; import static org.briarproject.api.forum.ForumConstants.KEY_ID; import static org.briarproject.api.forum.ForumConstants.KEY_LOCAL; import static org.briarproject.api.forum.ForumConstants.KEY_NAME; @@ -132,7 +131,6 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { authorMeta.put(KEY_PUBLIC_NAME, a.getPublicKey()); meta.put(KEY_AUTHOR, authorMeta); } - meta.put(KEY_CONTENT_TYPE, p.getContentType()); meta.put(KEY_LOCAL, true); meta.put(KEY_READ, true); clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); @@ -236,7 +234,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { private Forum parseForum(Group g) throws FormatException { byte[] descriptor = g.getDescriptor(); // Name, salt - BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length); + BdfList forum = clientHelper.toList(descriptor); return new Forum(g, forum.getString(0), forum.getRaw(1)); } @@ -262,11 +260,10 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { identityManager.getAuthorStatus(txn, author.getId()); } } - String contentType = meta.getString(KEY_CONTENT_TYPE); boolean read = meta.getBoolean(KEY_READ); return new ForumPostHeader(id, parentId, timestamp, author, - authorStatus, contentType, read); + authorStatus, read); } private ForumPostHeader getForumPostHeader(MessageId id, diff --git a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java index c3c2067f1..a6c0b226a 100644 --- a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java @@ -44,7 +44,7 @@ class ForumPostFactoryImpl implements ForumPostFactory { // Serialise the message BdfList message = BdfList.of(parent, null, contentType, body, null); Message m = clientHelper.createMessage(groupId, timestamp, message); - return new ForumPost(m, parent, null, contentType); + return new ForumPost(m, parent, null); } @Override @@ -71,6 +71,6 @@ class ForumPostFactoryImpl implements ForumPostFactory { BdfList message = BdfList.of(parent, authorList, contentType, body, sig); Message m = clientHelper.createMessage(groupId, timestamp, message); - return new ForumPost(m, parent, author, contentType); + return new ForumPost(m, parent, author); } } diff --git a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java index 637be2dbf..f2531a6c6 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java @@ -5,6 +5,7 @@ import org.briarproject.api.FormatException; import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogPost; +import org.briarproject.api.blogs.BlogPostFactory; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.Contact; @@ -33,17 +34,20 @@ import org.junit.Test; import java.util.Collection; import java.util.Collections; +import javax.inject.Inject; + import static org.briarproject.TestUtils.getRandomBytes; import static org.briarproject.TestUtils.getRandomId; 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_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; 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.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; +import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; +import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID; @@ -68,9 +72,13 @@ public class BlogManagerImplTest extends BriarTestCase { private final Message message; private final MessageId messageId; + @Inject + @SuppressWarnings("WeakerAccess") + BlogPostFactory blogPostFactory; + public BlogManagerImplTest() { blogManager = new BlogManagerImpl(db, identityManager, clientHelper, - metadataParser, contactManager, blogFactory); + metadataParser, contactManager, blogFactory, blogPostFactory); blog1 = getBlog("Test Blog 1", "Test Description 1"); blog2 = getBlog("Test Blog 2", "Test Description 2"); @@ -183,12 +191,11 @@ public class BlogManagerImplTest extends BriarTestCase { BdfList list = new BdfList(); BdfDictionary author = authorToBdfDictionary(blog1.getAuthor()); BdfDictionary meta = BdfDictionary.of( + new BdfEntry(KEY_TYPE, POST.getInt()), new BdfEntry(KEY_TIMESTAMP, 0), new BdfEntry(KEY_TIME_RECEIVED, 1), new BdfEntry(KEY_AUTHOR, author), - new BdfEntry(KEY_CONTENT_TYPE, 0), - new BdfEntry(KEY_READ, false), - new BdfEntry(KEY_CONTENT_TYPE, "text/plain") + new BdfEntry(KEY_READ, false) ); context.checking(new Expectations() {{ @@ -211,7 +218,6 @@ public class BlogManagerImplTest extends BriarTestCase { assertEquals(messageId, h.getId()); assertEquals(null, h.getParentId()); assertEquals(VERIFIED, h.getAuthorStatus()); - assertEquals("text/plain", h.getContentType()); assertEquals(blog1.getAuthor(), h.getAuthor()); } @@ -273,13 +279,12 @@ public class BlogManagerImplTest extends BriarTestCase { public void testAddLocalPost() throws DbException, FormatException { final Transaction txn = new Transaction(null, true); final BlogPost post = - new BlogPost(null, message, null, blog1.getAuthor(), - "text/plain"); + new BlogPost(message, null, blog1.getAuthor()); BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor()); final BdfDictionary meta = BdfDictionary.of( + new BdfEntry(KEY_TYPE, POST.getInt()), new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()), new BdfEntry(KEY_AUTHOR, authorMeta), - new BdfEntry(KEY_CONTENT_TYPE, "text/plain"), new BdfEntry(KEY_READ, true) ); @@ -308,7 +313,6 @@ public class BlogManagerImplTest extends BriarTestCase { assertEquals(messageId, h.getId()); assertEquals(null, h.getParentId()); assertEquals(VERIFIED, h.getAuthorStatus()); - assertEquals("text/plain", h.getContentType()); assertEquals(blog1.getAuthor(), h.getAuthor()); } diff --git a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java index e64f29ba6..a6fa5f73e 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java @@ -37,12 +37,10 @@ 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_CONTENT_TYPE; -import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID; +import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_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.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; @@ -70,8 +68,7 @@ public class BlogPostValidatorTest extends BriarTestCase { private final BlogFactory blogFactory = context.mock(BlogFactory.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final Clock clock = new SystemClock(); - private final byte[] body = TestUtils.getRandomBytes( - MAX_BLOG_POST_BODY_LENGTH); + private final String body = TestUtils.getRandomString(42); private final String contentType = "text/plain"; public BlogPostValidatorTest() { @@ -105,18 +102,15 @@ public class BlogPostValidatorTest extends BriarTestCase { @Test public void testValidateProperBlogPost() throws IOException, GeneralSecurityException { - // content type, title (optional), post body, attachments - BdfList content = BdfList.of(null, contentType, null, body, null); final byte[] sigBytes = TestUtils.getRandomBytes(42); - BdfList m = BdfList.of(POST.getInt(), content, sigBytes); + BdfList m = BdfList.of(POST.getInt(), body, sigBytes); BdfList signed = - BdfList.of(blog.getId(), message.getTimestamp(), content); + BdfList.of(blog.getId(), message.getTimestamp(), body); expectCrypto(signed, sigBytes, true); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); - assertEquals(contentType, result.getString(KEY_CONTENT_TYPE)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertFalse(result.getBoolean(KEY_READ)); context.assertIsSatisfied(); @@ -143,13 +137,11 @@ public class BlogPostValidatorTest extends BriarTestCase { @Test(expected = InvalidMessageException.class) public void testValidateBlogPostWithBadSignature() throws IOException, GeneralSecurityException { - // content type, title (optional), post body, attachments - BdfList content = BdfList.of(null, contentType, null, body, null); final byte[] sigBytes = TestUtils.getRandomBytes(42); - BdfList m = BdfList.of(POST.getInt(), content, sigBytes); + BdfList m = BdfList.of(POST.getInt(), body, sigBytes); BdfList signed = - BdfList.of(blog.getId(), message.getTimestamp(), content); + BdfList.of(blog.getId(), message.getTimestamp(), body); expectCrypto(signed, sigBytes, false); validator.validateMessage(message, group, m).getDictionary(); } @@ -157,17 +149,18 @@ public class BlogPostValidatorTest extends BriarTestCase { @Test public void testValidateProperBlogComment() throws IOException, GeneralSecurityException { - // comment, parent_original_id, signature, parent_current_id + // comment, parent_original_id, parent_id, signature String comment = "This is a blog comment"; MessageId originalId = new MessageId(TestUtils.getRandomId()); - byte[] currentId = TestUtils.getRandomId(); + MessageId currentId = new MessageId(TestUtils.getRandomId()); final byte[] sigBytes = TestUtils.getRandomBytes(42); - BdfList m = BdfList.of(COMMENT.getInt(), comment, originalId, - sigBytes, currentId); + BdfList m = + BdfList.of(COMMENT.getInt(), comment, originalId, currentId, + sigBytes); BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), comment, - originalId); + originalId, currentId); expectCrypto(signed, sigBytes, true); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); @@ -175,7 +168,7 @@ public class BlogPostValidatorTest extends BriarTestCase { assertEquals(comment, result.getString(KEY_COMMENT)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); - assertEquals(currentId, result.getRaw(KEY_CURRENT_MSG_ID)); + assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID)); assertFalse(result.getBoolean(KEY_READ)); context.assertIsSatisfied(); } @@ -185,14 +178,15 @@ public class BlogPostValidatorTest extends BriarTestCase { throws IOException, GeneralSecurityException { // comment, parent_original_id, signature, parent_current_id MessageId originalId = new MessageId(TestUtils.getRandomId()); - byte[] currentId = TestUtils.getRandomId(); + MessageId currentId = new MessageId(TestUtils.getRandomId()); final byte[] sigBytes = TestUtils.getRandomBytes(42); - BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, sigBytes, - currentId); + BdfList m = + BdfList.of(COMMENT.getInt(), null, originalId, currentId, + sigBytes); BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), null, - originalId); + originalId, currentId); expectCrypto(signed, sigBytes, true); final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); @@ -205,17 +199,16 @@ public class BlogPostValidatorTest extends BriarTestCase { public void testValidateProperWrappedPost() throws IOException, GeneralSecurityException { // group descriptor, timestamp, content, signature - BdfList content = BdfList.of(null, contentType, null, body, null); final byte[] sigBytes = TestUtils.getRandomBytes(42); BdfList m = BdfList.of(WRAPPED_POST.getInt(), descriptor, - message.getTimestamp(), content, sigBytes); + message.getTimestamp(), body, sigBytes); BdfList signed = - BdfList.of(blog.getId(), message.getTimestamp(), content); + BdfList.of(blog.getId(), message.getTimestamp(), body); expectCrypto(signed, sigBytes, true); - final BdfList originalList = BdfList.of(content, sigBytes); + final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes); final byte[] originalBody = TestUtils.getRandomBytes(42); context.checking(new Expectations() {{ @@ -232,7 +225,6 @@ public class BlogPostValidatorTest extends BriarTestCase { final BdfDictionary result = validator.validateMessage(message, group, m).getDictionary(); - assertEquals(contentType, result.getString(KEY_CONTENT_TYPE)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); context.assertIsSatisfied(); } @@ -244,18 +236,19 @@ public class BlogPostValidatorTest extends BriarTestCase { // parent_current_id String comment = "This is another comment"; MessageId originalId = new MessageId(TestUtils.getRandomId()); + MessageId oldId = new MessageId(TestUtils.getRandomId()); final byte[] sigBytes = TestUtils.getRandomBytes(42); MessageId currentId = new MessageId(TestUtils.getRandomId()); BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, - message.getTimestamp(), comment, originalId, sigBytes, + message.getTimestamp(), comment, originalId, oldId, sigBytes, currentId); BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), - comment, originalId); + comment, originalId, oldId); expectCrypto(signed, sigBytes, true); - final BdfList originalList = BdfList.of(comment, originalId, sigBytes, - currentId); + final BdfList originalList = BdfList.of(COMMENT.getInt(), comment, + originalId, oldId, sigBytes); final byte[] originalBody = TestUtils.getRandomBytes(42); context.checking(new Expectations() {{ @@ -276,7 +269,7 @@ public class BlogPostValidatorTest extends BriarTestCase { assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals( message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); - assertEquals(currentId.getBytes(), result.getRaw(KEY_CURRENT_MSG_ID)); + assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID)); context.assertIsSatisfied(); } From 65bdd5558e81cc55e8ad65331520a9b80826a1eb Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 30 Aug 2016 12:48:57 -0300 Subject: [PATCH 3/3] Address bugs and comments from reblogging code review --- .../api/blogs/BlogCommentHeader.java | 5 +- .../briarproject/api/blogs/BlogConstants.java | 6 +- .../api/blogs/BlogPostFactory.java | 8 +- .../briarproject/blogs/BlogManagerImpl.java | 111 +++++++++--------- .../blogs/BlogPostFactoryImpl.java | 47 +++++--- .../briarproject/blogs/BlogPostValidator.java | 35 +++--- .../blogs/BlogPostValidatorTest.java | 6 +- 7 files changed, 117 insertions(+), 101 deletions(-) diff --git a/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java index 9ed79b910..a5d2e4e9d 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java @@ -12,8 +12,8 @@ import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; public class BlogCommentHeader extends BlogPostHeader { - private String comment; - private BlogPostHeader parent; + private final String comment; + private final BlogPostHeader parent; public BlogCommentHeader(@NotNull MessageType type, @NotNull GroupId groupId, @Nullable String comment, @@ -36,7 +36,6 @@ public class BlogCommentHeader extends BlogPostHeader { return comment; } - @NotNull public BlogPostHeader getParent() { return parent; } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java index 30d46fa76..13e450c1c 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java @@ -35,6 +35,10 @@ public interface BlogConstants { String KEY_COMMENT = "comment"; String KEY_ORIGINAL_MSG_ID = "originalMessageId"; String KEY_ORIGINAL_PARENT_MSG_ID = "originalParentMessageId"; - String KEY_WRAPPED_MSG_ID = "wrappedMessageId"; + /** + * This is the ID of either a message wrapped from a different group + * or of a message from the same group that therefore needed no wrapping. + */ + String KEY_PARENT_MSG_ID = "parentMessageId"; } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java index 46907cadc..7b05c0078 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java @@ -23,20 +23,20 @@ public interface BlogPostFactory { throws FormatException, GeneralSecurityException; /** Wraps a blog post */ - Message createWrappedPost(GroupId groupId, byte[] descriptor, + Message wrapPost(GroupId groupId, byte[] descriptor, long timestamp, BdfList body) throws FormatException; /** Re-wraps a previously wrapped post */ - Message createWrappedPost(GroupId groupId, BdfList body) + Message rewrapWrappedPost(GroupId groupId, BdfList body) throws FormatException; /** Wraps a blog comment */ - Message createWrappedComment(GroupId groupId, byte[] descriptor, + Message wrapComment(GroupId groupId, byte[] descriptor, long timestamp, BdfList body, MessageId currentId) throws FormatException; /** Re-wraps a previously wrapped comment */ - Message createWrappedComment(GroupId groupId, BdfList body, + Message rewrapWrappedComment(GroupId groupId, BdfList body, MessageId currentId) throws FormatException; } diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index fc278c556..2720910e0 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -64,7 +64,7 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; -import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_MSG_ID; +import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID; import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; @@ -189,12 +189,12 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, BdfDictionary d = clientHelper .getMessageMetadataAsDictionary(txn, h.getParentId()); byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID); - byte[] original2 = meta.getRaw(KEY_ORIGINAL_MSG_ID); + byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID); if (!Arrays.equals(original1, original2)) { throw new FormatException(); } } - // share dependencies recursively + // share dependencies recursively - TODO remove with #598 share(txn, h); // broadcast event about new post or comment @@ -205,7 +205,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, // Check that the original message ID in the dependency's metadata // matches the original parent ID of the wrapped comment MessageId dependencyId = - new MessageId(meta.getRaw(KEY_WRAPPED_MSG_ID)); + new MessageId(meta.getRaw(KEY_PARENT_MSG_ID)); BdfDictionary d = clientHelper .getMessageMetadataAsDictionary(txn, dependencyId); byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID); @@ -305,7 +305,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, GroupId groupId = p.getMessage().getGroupId(); MessageId postId = p.getMessage().getId(); BlogPostHeader h = - getPostHeaderFromMetadata(txn, 0, groupId, postId, meta); + getPostHeaderFromMetadata(txn, groupId, postId, meta); BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); txn.attach(event); @@ -316,37 +316,33 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, @Override public void addLocalComment(LocalAuthor author, GroupId groupId, - @Nullable String comment, BlogPostHeader wHeader) + @Nullable String comment, BlogPostHeader pOriginalHeader) throws DbException { - if (wHeader.getType() != POST && wHeader.getType() != COMMENT) + MessageType type = pOriginalHeader.getType(); + if (type != POST && type != COMMENT) throw new IllegalArgumentException("Comment on unknown type!"); Transaction txn = db.startTransaction(false); try { // Wrap post that we are commenting on - MessageId wMessageId = wrapMessage(txn, 0, groupId, wHeader); + MessageId parentId = wrapMessage(txn, groupId, pOriginalHeader); - // Get ID of original message - MessageId originalId = wHeader.getId(); - if (wHeader.getType() != POST && wMessageId.equals(originalId)) { - // we comment on a non-post message that needs no wrapping, - // so get original message ID from it - BdfDictionary d = clientHelper - .getMessageMetadataAsDictionary(txn, wMessageId); - originalId = new MessageId(d.getRaw(KEY_ORIGINAL_MSG_ID)); - } + // Get ID of new parent's original message. + // Assumes that pOriginalHeader is a POST or COMMENT + MessageId pOriginalId = pOriginalHeader.getId(); // Create actual comment Message message = blogPostFactory - .createBlogComment(groupId, author, comment, originalId, - wMessageId); + .createBlogComment(groupId, author, comment, pOriginalId, + parentId); BdfDictionary meta = new BdfDictionary(); meta.put(KEY_TYPE, COMMENT.getInt()); if (comment != null) meta.put(KEY_COMMENT, comment); meta.put(KEY_TIMESTAMP, message.getTimestamp()); - meta.put(KEY_ORIGINAL_MSG_ID, wHeader.getId()); - meta.put(KEY_WRAPPED_MSG_ID, wMessageId); + meta.put(KEY_ORIGINAL_MSG_ID, message.getId()); + meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId); + meta.put(KEY_PARENT_MSG_ID, parentId); meta.put(KEY_AUTHOR, authorToBdfDictionary(author)); // Send comment @@ -362,72 +358,75 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } catch (FormatException e) { throw new DbException(e); } catch (GeneralSecurityException e) { - throw new DbException(e); + throw new IllegalArgumentException("Invalid key of author", e); } finally { //noinspection ThrowFromFinallyBlock db.endTransaction(txn); } } - private MessageId wrapMessage(Transaction txn, int level, GroupId groupId, - BlogPostHeader wHeader) + private MessageId wrapMessage(Transaction txn, GroupId groupId, + BlogPostHeader pOriginalHeader) throws DbException, FormatException { - if (groupId.equals(wHeader.getGroupId())) { + if (groupId.equals(pOriginalHeader.getGroupId())) { // We are trying to wrap a post that is already in our group. // This is unnecessary, so just return the post's MessageId - return wHeader.getId(); + return pOriginalHeader.getId(); } - // Get Group and Body of Message to be wrapped - Group wGroup = db.getGroup(txn, wHeader.getGroupId()); - BdfList wBody = clientHelper.getMessageAsList(txn, wHeader.getId()); - byte[] wDescriptor = wGroup.getDescriptor(); - long wTimestamp = wHeader.getTimestamp(); + // Get body of message to be wrapped + BdfList body = + clientHelper.getMessageAsList(txn, pOriginalHeader.getId()); + long wTimestamp = pOriginalHeader.getTimestamp(); Message wMessage; BdfDictionary meta = new BdfDictionary(); - MessageType type = wHeader.getType(); + MessageType type = pOriginalHeader.getType(); if (type == POST) { + Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); + byte[] wDescriptor = wGroup.getDescriptor(); // Wrap post wMessage = blogPostFactory - .createWrappedPost(groupId, wDescriptor, wTimestamp, wBody); + .wrapPost(groupId, wDescriptor, wTimestamp, body); meta.put(KEY_TYPE, WRAPPED_POST.getInt()); } else if (type == COMMENT) { - BlogCommentHeader wComment = (BlogCommentHeader) wHeader; + Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); + byte[] wDescriptor = wGroup.getDescriptor(); + BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; MessageId wrappedId = - wrapMessage(txn, ++level, groupId, wComment.getParent()); + wrapMessage(txn, groupId, wComment.getParent()); // Wrap comment wMessage = blogPostFactory - .createWrappedComment(groupId, wDescriptor, wTimestamp, - wBody, wrappedId); + .wrapComment(groupId, wDescriptor, wTimestamp, + body, wrappedId); meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); if(wComment.getComment() != null) meta.put(KEY_COMMENT, wComment.getComment()); - meta.put(KEY_WRAPPED_MSG_ID, wrappedId); + meta.put(KEY_PARENT_MSG_ID, wrappedId); } else if (type == WRAPPED_POST) { // Re-wrap wrapped post without adding another wrapping layer - wMessage = blogPostFactory.createWrappedPost(groupId, wBody); + wMessage = blogPostFactory.rewrapWrappedPost(groupId, body); meta.put(KEY_TYPE, WRAPPED_POST.getInt()); } else if (type == WRAPPED_COMMENT) { - BlogCommentHeader wComment = (BlogCommentHeader) wHeader; + BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; MessageId wrappedId = - wrapMessage(txn, ++level, groupId, wComment.getParent()); + wrapMessage(txn, groupId, wComment.getParent()); // Re-wrap wrapped comment wMessage = blogPostFactory - .createWrappedComment(groupId, wBody, wrappedId); + .rewrapWrappedComment(groupId, body, wrappedId); meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); if(wComment.getComment() != null) meta.put(KEY_COMMENT, wComment.getComment()); - meta.put(KEY_WRAPPED_MSG_ID, wrappedId); + meta.put(KEY_PARENT_MSG_ID, wrappedId); } else { throw new IllegalArgumentException( "Unknown Message Type: " + type); } - meta.put(KEY_ORIGINAL_MSG_ID, wHeader.getId()); - meta.put(KEY_AUTHOR, authorToBdfDictionary(wHeader.getAuthor())); - meta.put(KEY_TIMESTAMP, wHeader.getTimestamp()); - meta.put(KEY_TIME_RECEIVED, wHeader.getTimeReceived()); + meta.put(KEY_ORIGINAL_MSG_ID, pOriginalHeader.getId()); + meta.put(KEY_AUTHOR, authorToBdfDictionary(pOriginalHeader.getAuthor())); + meta.put(KEY_TIMESTAMP, pOriginalHeader.getTimestamp()); + meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived()); // Send wrapped message and store metadata clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true); @@ -553,7 +552,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, new BdfEntry(KEY_TYPE, COMMENT.getInt()) ); - // TODO this could be optimized to re-use existing headers + // TODO this could be optimized by looking up author status once (#625) Collection headers = new ArrayList(); Transaction txn = db.startTransaction(true); @@ -605,19 +604,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, - GroupId groupId, MessageId id, BdfDictionary meta) - throws DbException, FormatException { - return getPostHeaderFromMetadata(txn, 0, groupId, id, meta); - } - - private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, int level, GroupId groupId, MessageId id) throws DbException, FormatException { BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(txn, id); - return getPostHeaderFromMetadata(txn, level, groupId, id, meta); + return getPostHeaderFromMetadata(txn, groupId, id, meta); } - private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, int level, + private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, GroupId groupId, MessageId id, BdfDictionary meta) throws DbException, FormatException { @@ -637,9 +630,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, if (type == COMMENT || type == WRAPPED_COMMENT) { String comment = meta.getOptionalString(KEY_COMMENT); - MessageId parentId = new MessageId(meta.getRaw(KEY_WRAPPED_MSG_ID)); + MessageId parentId = new MessageId(meta.getRaw(KEY_PARENT_MSG_ID)); BlogPostHeader parent = - getPostHeaderFromMetadata(txn, ++level, groupId, parentId); + getPostHeaderFromMetadata(txn, groupId, parentId); return new BlogCommentHeader(type, groupId, comment, parent, id, timestamp, timeReceived, author, authorStatus, read); } else { @@ -653,6 +646,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, return MessageType.valueOf(longType.intValue()); } + // TODO remove when implementing #589 + @Deprecated private void share(Transaction txn, BlogPostHeader h) throws DbException { clientHelper.setMessageShared(txn, h.getId(), true); if (h instanceof BlogCommentHeader) { diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java index 5d55c2ddb..62e6bc0ad 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -3,6 +3,7 @@ package org.briarproject.blogs; import org.briarproject.api.FormatException; import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPostFactory; +import org.briarproject.api.blogs.MessageType; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.data.BdfList; import org.briarproject.api.identity.LocalAuthor; @@ -58,28 +59,31 @@ class BlogPostFactoryImpl implements BlogPostFactory { @Override public Message createBlogComment(GroupId groupId, LocalAuthor author, - @Nullable String comment, MessageId originalId, MessageId wrappedId) + @Nullable String comment, MessageId pOriginalId, MessageId parentId) throws FormatException, GeneralSecurityException { long timestamp = clock.currentTimeMillis(); // Generate the signature BdfList signed = - BdfList.of(groupId, timestamp, comment, originalId, wrappedId); + BdfList.of(groupId, timestamp, comment, pOriginalId, parentId); byte[] sig = clientHelper.sign(signed, author.getPrivateKey()); // Serialise the signed message BdfList message = - BdfList.of(COMMENT.getInt(), comment, originalId, wrappedId, + BdfList.of(COMMENT.getInt(), comment, pOriginalId, parentId, sig); return clientHelper.createMessage(groupId, timestamp, message); } @Override - public Message createWrappedPost(GroupId groupId, byte[] descriptor, + public Message wrapPost(GroupId groupId, byte[] descriptor, long timestamp, BdfList body) throws FormatException { + if (getType(body) != POST) + throw new IllegalArgumentException("Needs to wrap a POST"); + // Serialise the message String content = body.getString(1); byte[] signature = body.getRaw(2); @@ -91,9 +95,12 @@ class BlogPostFactoryImpl implements BlogPostFactory { } @Override - public Message createWrappedPost(GroupId groupId, BdfList body) + public Message rewrapWrappedPost(GroupId groupId, BdfList body) throws FormatException { + if (getType(body) != WRAPPED_POST) + throw new IllegalArgumentException("Needs to wrap a WRAPPED_POST"); + // Serialise the message byte[] descriptor = body.getRaw(1); long timestamp = body.getLong(2); @@ -107,38 +114,48 @@ class BlogPostFactoryImpl implements BlogPostFactory { } @Override - public Message createWrappedComment(GroupId groupId, byte[] descriptor, - long timestamp, BdfList body, MessageId currentId) + public Message wrapComment(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body, MessageId parentId) throws FormatException { + if (getType(body) != COMMENT) + throw new IllegalArgumentException("Needs to wrap a COMMENT"); + // Serialise the message String comment = body.getOptionalString(1); - byte[] originalId = body.getRaw(2); - byte[] oldId = body.getRaw(3); + byte[] pOriginalId = body.getRaw(2); + byte[] oldParentId = body.getRaw(3); byte[] signature = body.getRaw(4); BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, - comment, originalId, oldId, signature, currentId); + comment, pOriginalId, oldParentId, signature, parentId); return clientHelper .createMessage(groupId, clock.currentTimeMillis(), message); } @Override - public Message createWrappedComment(GroupId groupId, BdfList body, - MessageId currentId) throws FormatException { + public Message rewrapWrappedComment(GroupId groupId, BdfList body, + MessageId parentId) throws FormatException { + + if (getType(body) != WRAPPED_COMMENT) + throw new IllegalArgumentException( + "Needs to wrap a WRAPPED_COMMENT"); // Serialise the message byte[] descriptor = body.getRaw(1); long timestamp = body.getLong(2); String comment = body.getOptionalString(3); - byte[] originalId = body.getRaw(4); - byte[] oldId = body.getRaw(5); + byte[] pOriginalId = body.getRaw(4); + byte[] oldParentId = body.getRaw(5); byte[] signature = body.getRaw(6); BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, - comment, originalId, oldId, signature, currentId); + comment, pOriginalId, oldParentId, signature, parentId); return clientHelper .createMessage(groupId, clock.currentTimeMillis(), message); } + private MessageType getType(BdfList body) throws FormatException { + return MessageType.valueOf(body.getLong(0).intValue()); + } } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java index 9014e76ba..eaab2313f 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -33,7 +33,7 @@ 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_PARENT_MSG_ID; -import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_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; @@ -129,9 +129,9 @@ class BlogPostValidator extends BdfMessageValidator { // parent_original_id // The ID of a post or comment in this group or another group - byte[] originalIdBytes = body.getRaw(1); - checkLength(originalIdBytes, MessageId.LENGTH); - MessageId originalId = new MessageId(originalIdBytes); + byte[] pOriginalIdBytes = body.getRaw(1); + checkLength(pOriginalIdBytes, MessageId.LENGTH); + MessageId pOriginalId = new MessageId(pOriginalIdBytes); // parent_id // The ID of a post, comment, wrapped post or wrapped comment in this @@ -145,7 +145,7 @@ class BlogPostValidator extends BdfMessageValidator { byte[] sig = body.getRaw(3); checkLength(sig, 0, MAX_SIGNATURE_LENGTH); BdfList signed = - BdfList.of(g.getId(), m.getTimestamp(), comment, originalId, + BdfList.of(g.getId(), m.getTimestamp(), comment, pOriginalId, currentId); Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter Author a = b.getAuthor(); @@ -154,8 +154,9 @@ class BlogPostValidator extends BdfMessageValidator { // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); if (comment != null) meta.put(KEY_COMMENT, comment); - meta.put(KEY_ORIGINAL_MSG_ID, originalId); - meta.put(KEY_WRAPPED_MSG_ID, currentId); + meta.put(KEY_ORIGINAL_MSG_ID, m.getId()); + meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId); + meta.put(KEY_PARENT_MSG_ID, currentId); meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); Collection dependencies = Collections.singleton(currentId); return new BdfMessageContext(meta, dependencies); @@ -219,9 +220,9 @@ class BlogPostValidator extends BdfMessageValidator { // c_parent_original_id // Taken from the original comment - byte[] originalIdBytes = body.getRaw(3); - checkLength(originalIdBytes, MessageId.LENGTH); - MessageId originalId = new MessageId(originalIdBytes); + byte[] pOriginalIdBytes = body.getRaw(3); + checkLength(pOriginalIdBytes, MessageId.LENGTH); + MessageId pOriginalId = new MessageId(pOriginalIdBytes); // c_parent_id // Taken from the original comment @@ -238,14 +239,14 @@ class BlogPostValidator extends BdfMessageValidator { // The ID of a post, comment, wrapped post or wrapped comment in this // group, which had the ID c_parent_original_id in the group // where it was originally posted - byte[] currentIdBytes = body.getRaw(6); - checkLength(currentIdBytes, MessageId.LENGTH); - MessageId currentId = new MessageId(currentIdBytes); + byte[] parentIdBytes = body.getRaw(6); + checkLength(parentIdBytes, MessageId.LENGTH); + MessageId parentId = new MessageId(parentIdBytes); // Get and Validate the Wrapped Comment Group wGroup = groupFactory .createGroup(BlogManagerImpl.CLIENT_ID, descriptor); - BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, originalId, + BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, pOriginalId, oldId, signature); byte[] wBody = clientHelper.toByteArray(wBodyList); Message wMessage = @@ -254,11 +255,11 @@ class BlogPostValidator extends BdfMessageValidator { BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList); // Return the metadata and dependencies - Collection dependencies = Collections.singleton(currentId); + Collection dependencies = Collections.singleton(parentId); BdfDictionary meta = new BdfDictionary(); meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId()); - meta.put(KEY_ORIGINAL_PARENT_MSG_ID, originalId); - meta.put(KEY_WRAPPED_MSG_ID, currentId); + meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId); + meta.put(KEY_PARENT_MSG_ID, parentId); meta.put(KEY_TIMESTAMP, wTimestamp); if (comment != null) meta.put(KEY_COMMENT, comment); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); diff --git a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java index a6fa5f73e..fae1f04d7 100644 --- a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java +++ b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java @@ -37,7 +37,7 @@ 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_WRAPPED_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; @@ -168,7 +168,7 @@ public class BlogPostValidatorTest extends BriarTestCase { assertEquals(comment, result.getString(KEY_COMMENT)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); - assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID)); + assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID)); assertFalse(result.getBoolean(KEY_READ)); context.assertIsSatisfied(); } @@ -269,7 +269,7 @@ public class BlogPostValidatorTest extends BriarTestCase { assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals( message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); - assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID)); + assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID)); context.assertIsSatisfied(); }