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 09e63bd98b87dcc11f26f84c045137a080f63d8f..e2893714dacc1bce8ab50b76684cfd9f2fd1f49d 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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<BlogPostHeader> 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 15552fdb1ac80bcba5149fa25509a24b76c86212..9906e511e80952f4a9c1352b405d38e7613e3b76 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 9c36a73a50c54d3f6276fa30bfe7deb0408ccdcc..f96deda0b2e9ecaeed0d79d3b9cea3adc0d2f3a8 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 c3aa850af251b88bb5b39092f014d3c57b33cdc8..55a439e2edfb148f28f8d44556f4f27c5188fea3 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<BlogPostItem> { return groupId; } - @Nullable - public String getTitle() { - return header.getTitle(); - } - public long getTimestamp() { return header.getTimestamp(); } @@ -72,11 +67,6 @@ class BlogPostItem implements Comparable<BlogPostItem> { 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 4e6d24312c74964e1b68a4004bd5843a2c82c9da..dc462331c764a6a04c4e9a5c39ab9892de342c9f 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 638074ca13753aa13438866160f96bbfe343144b..2ec3bc09be9256e004949e1446c77f04aeffa0bc 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 0000000000000000000000000000000000000000..a5d2e4e9df68def851974bb3a896e03c21aefb1f --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java @@ -0,0 +1,42 @@ +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 final String comment; + private final 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; + } + + 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 5a9bff7f4e0c3b1b0c544964c2d690397f3e1d04..13e450c1c77843198469dfc8746afbbf09dd64f4 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,20 @@ 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"; + /** + * 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/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java index 1d4f0eaeebee2cf20fc018851c8aa1b6b6fe8f23..ac8ea516b47f4269a51f8fca2c0d68a4d0fc1437 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<Blog> 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 3854c047279b90f2718a39644cee381893ad9a5b..73c787b29dbd1776b9bf0eff9e75db144411a69c 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 6ced9ba20fe646822eced9d1269648f0740e51f2..7b05c0078ec5d5ba84e86412e5291020b1c196a1 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 wrapPost(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body) + throws FormatException; + + /** Re-wraps a previously wrapped post */ + Message rewrapWrappedPost(GroupId groupId, BdfList body) + throws FormatException; + + /** Wraps a blog comment */ + Message wrapComment(GroupId groupId, byte[] descriptor, + long timestamp, BdfList body, MessageId currentId) + throws FormatException; + + /** Re-wraps a previously wrapped comment */ + Message rewrapWrappedComment(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 f9ef7bdfeaa7d6e5184b469734d2ba167c8f0b50..dc191078dfa8af7cd0bbfc44ff81544c00cc718a 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 a3524d2767d17e188249ec3390b140c283e4021a..c6553c91bb650e25ee96040f0edb6ba8e65ec068 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 { @@ -72,7 +73,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; @@ -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 33f0d28771c9ecc7f30eeb87798d18b3b3f4c1b2..7ffed63645bb5a057712cdb30b389a86a17e50dd 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/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index 735f64a21fedb927bcc60b3d8cfb8d83339b8fe3..d3dbace30d300d794fa593ab6ecec34f0ff4d7cb 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 d011e8c3ebe5394e2dccb13c22f423c827db725e..840c7cc42d6808497f98bea0cbde985e008ccbb5 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-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java index 9523b20cc8eb1fbdd73974a6461671acc19a08a6..97557d5fe405fbefa3cdb45899e6ebeeb2219248 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 404d6c520e8cb33661298fa48a7acc123c278e31..6e433137b4edf6270aec7bdae2d407126fabee9d 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 c25a9e251b1436e3beda8207656abba1112a0674..ef08dc022fa32d758bca4d0280b8a6a541fa22f7 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 82f7d7ab2848104dc492f169e184194f69c24e1c..7853ec403c2cb08321266bc7914eeefc5c826a54 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 097e594f46c53dc8ad614cca33475b610b94678e..2720910e0c1f1ca77a7f3e93fc2950f582215fc3 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_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; +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<RemoveBlogHook> 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<RemoveBlogHook>(); } @@ -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_PARENT_MSG_ID); + if (!Arrays.equals(original1, original2)) { + throw new FormatException(); + } + } + // share dependencies recursively - TODO remove with #598 + 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_PARENT_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, groupId, postId, meta); BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); txn.attach(event); @@ -274,6 +314,125 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } + @Override + public void addLocalComment(LocalAuthor author, GroupId groupId, + @Nullable String comment, BlogPostHeader pOriginalHeader) + throws DbException { + + 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 parentId = wrapMessage(txn, groupId, pOriginalHeader); + + // 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, 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, 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 + 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 IllegalArgumentException("Invalid key of author", e); + } finally { + //noinspection ThrowFromFinallyBlock + db.endTransaction(txn); + } + } + + private MessageId wrapMessage(Transaction txn, GroupId groupId, + BlogPostHeader pOriginalHeader) + throws DbException, FormatException { + + 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 pOriginalHeader.getId(); + } + + // 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 = pOriginalHeader.getType(); + if (type == POST) { + Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); + byte[] wDescriptor = wGroup.getDescriptor(); + // Wrap post + wMessage = blogPostFactory + .wrapPost(groupId, wDescriptor, wTimestamp, body); + meta.put(KEY_TYPE, WRAPPED_POST.getInt()); + } else if (type == COMMENT) { + Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); + byte[] wDescriptor = wGroup.getDescriptor(); + BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; + MessageId wrappedId = + wrapMessage(txn, groupId, wComment.getParent()); + // Wrap comment + wMessage = blogPostFactory + .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_PARENT_MSG_ID, wrappedId); + } else if (type == WRAPPED_POST) { + // Re-wrap wrapped post without adding another wrapping layer + wMessage = blogPostFactory.rewrapWrappedPost(groupId, body); + meta.put(KEY_TYPE, WRAPPED_POST.getInt()); + } else if (type == WRAPPED_COMMENT) { + BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; + MessageId wrappedId = + wrapMessage(txn, groupId, wComment.getParent()); + // Re-wrap wrapped comment + wMessage = blogPostFactory + .rewrapWrappedComment(groupId, body, wrappedId); + meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); + if(wComment.getComment() != null) + meta.put(KEY_COMMENT, wComment.getComment()); + meta.put(KEY_PARENT_MSG_ID, wrappedId); + } else { + throw new IllegalArgumentException( + "Unknown Message Type: " + type); + } + 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); + return wMessage.getId(); + } + @Override public Blog getBlog(GroupId g) throws DbException { Blog blog; @@ -340,12 +499,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 +526,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<BlogPostHeader> 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 by looking up author status once (#625) + + Collection<BlogPostHeader> headers = new ArrayList<BlogPostHeader>(); Transaction txn = db.startTransaction(true); try { + Map<MessageId, BdfDictionary> metadata1 = + clientHelper.getMessageMetadataAsDictionary(txn, g, query1); + Map<MessageId, BdfDictionary> metadata2 = + clientHelper.getMessageMetadataAsDictionary(txn, g, query2); Map<MessageId, BdfDictionary> metadata = - clientHelper.getMessageMetadataAsDictionary(txn, g); - List<BlogPostHeader> headers = new ArrayList<BlogPostHeader>(); + new HashMap<MessageId, BdfDictionary>( + metadata1.size() + metadata2.size()); + metadata.putAll(metadata1); + metadata.putAll(metadata2); for (Entry<MessageId, BdfDictionary> 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 +604,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, - MessageId id, BdfDictionary meta) + GroupId groupId, MessageId id) throws DbException, FormatException { + BdfDictionary meta = + clientHelper.getMessageMetadataAsDictionary(txn, id); + return getPostHeaderFromMetadata(txn, groupId, id, meta); + } + + private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, + 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 +624,35 @@ 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); + + 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_PARENT_MSG_ID)); + BlogPostHeader parent = + getPostHeaderFromMetadata(txn, 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); + } + } - String contentType = meta.getString(KEY_CONTENT_TYPE); - boolean read = meta.getBoolean(KEY_READ); - return new BlogPostHeader(title, id, timestamp, timeReceived, author, - authorStatus, contentType, read); + private MessageType getMessageType(BdfDictionary d) throws FormatException { + Long longType = d.getLong(KEY_TYPE); + 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) { + 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 ce134d33c6214368f592037a545a605266f1ec88..62e6bc0addaecdd2e868bd780d68205f54f08e36 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -3,17 +3,14 @@ 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.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 +19,143 @@ 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 pOriginalId, MessageId parentId) + throws FormatException, GeneralSecurityException { + + long timestamp = clock.currentTimeMillis(); + + // Generate the signature + BdfList signed = + 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, pOriginalId, parentId, + sig); + return clientHelper.createMessage(groupId, timestamp, message); + } + + @Override + 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); + BdfList message = + BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp, + content, signature); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + + @Override + 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); + 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 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[] pOriginalId = body.getRaw(2); + byte[] oldParentId = body.getRaw(3); + byte[] signature = body.getRaw(4); + BdfList message = + BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, + comment, pOriginalId, oldParentId, signature, parentId); + return clientHelper + .createMessage(groupId, clock.currentTimeMillis(), message); + } + + @Override + 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[] pOriginalId = body.getRaw(4); + byte[] oldParentId = body.getRaw(5); + byte[] signature = body.getRaw(6); + BdfList message = + BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, + 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 15b74adbe4f36d3b980dfedb5b62f1d16f3f92d9..eaab2313fe3ba592f6fc64bd6eb911a75373d446 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_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.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,83 +99,64 @@ 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 - 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 + // group, which had the ID parent_original_id in the group + // where it was originally posted + byte[] currentIdBytes = body.getRaw(2); + checkLength(currentIdBytes, MessageId.LENGTH); + MessageId currentId = new MessageId(currentIdBytes); // Signature - byte[] sig = body.getRaw(2); + 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(); verifySignature(sig, a.getPublicKey(), signed); - // parent_current_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); - checkLength(currentIdBytes, MessageId.LENGTH); - MessageId currentId = new MessageId(currentIdBytes); - // 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_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<MessageId> dependencies = Collections.singleton(currentId); return new BdfMessageContext(meta, dependencies); @@ -193,7 +165,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 +173,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 +185,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); + byte[] pOriginalIdBytes = body.getRaw(3); + checkLength(pOriginalIdBytes, MessageId.LENGTH); + MessageId pOriginalId = new MessageId(pOriginalIdBytes); - // 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); - 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, originalId, signature, - currentId); + BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, pOriginalId, + 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<MessageId> dependencies = Collections.singleton(currentId); + Collection<MessageId> dependencies = Collections.singleton(parentId); 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, 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-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java index a93d39aa4534f14cd5f56807776432677426a87d..91cc68c7c5ff2d5052367aff744d16ace49a2293 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 00778b6b6dab5391c14bf6eff58db8a49ca6f66c..a8af6137ab93a3bd191c8d92a9fb976f15a9264f 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 @@ -240,7 +247,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); } @@ -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 302939ff3047143e9828d96f9f6d29e41761bacb..1262632c07ade0ade99c6cbac9e2830d4f6b72b1 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/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 1039a69e07bfa5467f48ac9084c29e15907afb47..01a2d9a96df25e5a22800ba37b888a66d729bf26 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<T> 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<T> 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/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java index 9379840da7fa8b18cd5c93a9881ef0ad47893609..344165b32f23ee4e0d1237f85fc34c84e1410362 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 84f28dbf995daf4099f25a8cbfba5f286859bde7..3e8b5de64eae11aa79330c5cf583341a789a982f 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; @@ -78,7 +77,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 = @@ -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 c3c2067f19170e3a5b41cd515deab31ad5d82b54..a6c0b226aea4109724a72ebb36ae02ca6299a937 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 9171103303cd56591afb0ea78d1f3c4f4054d0a3..f2531a6c6b795ba95cf4af3b5bc185ebb21f28b5 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,16 +191,15 @@ 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() {{ - oneOf(clientHelper).setMessageShared(txn, message, true); + oneOf(clientHelper).setMessageShared(txn, messageId, true); oneOf(identityManager) .getAuthorStatus(txn, blog1.getAuthor().getId()); will(returnValue(VERIFIED)); @@ -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 e64f29ba6b65ce2ad4fb262d80d1e449a04d5ff3..fae1f04d7b7847ac88098d69c3fcf690011faeb2 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_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_READ; -import static org.briarproject.api.blogs.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_PARENT_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_PARENT_MSG_ID)); context.assertIsSatisfied(); } diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index 30f46c03280ba350a99468dac35dd576a6b75be7..123ca114c0cb89539a704087d043f89eca2fe527 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