diff --git a/briar-android/artwork/trust-indicator.svg b/briar-android/artwork/trust-indicator.svg
new file mode 100644
index 000000000..c8f94b6f6
--- /dev/null
+++ b/briar-android/artwork/trust-indicator.svg
@@ -0,0 +1,92 @@
+
+
+
+
diff --git a/briar-android/res/drawable-hdpi/identity_anonymous.png b/briar-android/res/drawable-hdpi/identity_anonymous.png
deleted file mode 100644
index aa539d31b..000000000
Binary files a/briar-android/res/drawable-hdpi/identity_anonymous.png and /dev/null differ
diff --git a/briar-android/res/drawable-hdpi/identity_unknown.png b/briar-android/res/drawable-hdpi/identity_unknown.png
deleted file mode 100644
index b098e93aa..000000000
Binary files a/briar-android/res/drawable-hdpi/identity_unknown.png and /dev/null differ
diff --git a/briar-android/res/drawable-hdpi/identity_unverified.png b/briar-android/res/drawable-hdpi/identity_unverified.png
deleted file mode 100644
index f9e248626..000000000
Binary files a/briar-android/res/drawable-hdpi/identity_unverified.png and /dev/null differ
diff --git a/briar-android/res/drawable-hdpi/identity_verified.png b/briar-android/res/drawable-hdpi/identity_verified.png
deleted file mode 100644
index 02e819fa1..000000000
Binary files a/briar-android/res/drawable-hdpi/identity_verified.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/identity_anonymous.png b/briar-android/res/drawable-mdpi/identity_anonymous.png
deleted file mode 100644
index c91a016de..000000000
Binary files a/briar-android/res/drawable-mdpi/identity_anonymous.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/identity_unknown.png b/briar-android/res/drawable-mdpi/identity_unknown.png
deleted file mode 100644
index 6ad29f8a0..000000000
Binary files a/briar-android/res/drawable-mdpi/identity_unknown.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/identity_unverified.png b/briar-android/res/drawable-mdpi/identity_unverified.png
deleted file mode 100644
index 9b8a13d4b..000000000
Binary files a/briar-android/res/drawable-mdpi/identity_unverified.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/identity_verified.png b/briar-android/res/drawable-mdpi/identity_verified.png
deleted file mode 100644
index 79cbaedc2..000000000
Binary files a/briar-android/res/drawable-mdpi/identity_verified.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/identity_anonymous.png b/briar-android/res/drawable-xhdpi/identity_anonymous.png
deleted file mode 100644
index c8f254fda..000000000
Binary files a/briar-android/res/drawable-xhdpi/identity_anonymous.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/identity_unknown.png b/briar-android/res/drawable-xhdpi/identity_unknown.png
deleted file mode 100644
index e3b91a858..000000000
Binary files a/briar-android/res/drawable-xhdpi/identity_unknown.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/identity_unverified.png b/briar-android/res/drawable-xhdpi/identity_unverified.png
deleted file mode 100644
index 85785c09c..000000000
Binary files a/briar-android/res/drawable-xhdpi/identity_unverified.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/identity_verified.png b/briar-android/res/drawable-xhdpi/identity_verified.png
deleted file mode 100644
index d93ae1df2..000000000
Binary files a/briar-android/res/drawable-xhdpi/identity_verified.png and /dev/null differ
diff --git a/briar-android/res/drawable/trust_indicator_anonymous.xml b/briar-android/res/drawable/trust_indicator_anonymous.xml
new file mode 100644
index 000000000..99ccadd1b
--- /dev/null
+++ b/briar-android/res/drawable/trust_indicator_anonymous.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/drawable/trust_indicator_unknown.xml b/briar-android/res/drawable/trust_indicator_unknown.xml
new file mode 100644
index 000000000..ec65b5a55
--- /dev/null
+++ b/briar-android/res/drawable/trust_indicator_unknown.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/drawable/trust_indicator_unverified.xml b/briar-android/res/drawable/trust_indicator_unverified.xml
new file mode 100644
index 000000000..3697685b0
--- /dev/null
+++ b/briar-android/res/drawable/trust_indicator_unverified.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/drawable/trust_indicator_verified.xml b/briar-android/res/drawable/trust_indicator_verified.xml
new file mode 100644
index 000000000..40a8ff035
--- /dev/null
+++ b/briar-android/res/drawable/trust_indicator_verified.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml
deleted file mode 100644
index 4fb0b9d6b..000000000
--- a/briar-android/res/layout/author_view.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/briar-android/res/layout/forum_discussion_cell.xml b/briar-android/res/layout/forum_discussion_cell.xml
index 57383a720..e8fbf8aa3 100644
--- a/briar-android/res/layout/forum_discussion_cell.xml
+++ b/briar-android/res/layout/forum_discussion_cell.xml
@@ -131,6 +131,9 @@
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/btn_reply"
android:layout_toLeftOf="@id/btn_reply"
+ android:layout_toRightOf="@+id/trustIndicator"
+ android:gravity="right|end"
+ android:maxLines="1"
android:padding="@dimen/margin_medium"
android:textSize="@dimen/text_size_tiny"
tools:text="2 replies"/>
@@ -140,13 +143,23 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/replies"
- android:layout_toLeftOf="@id/replies"
android:layout_toRightOf="@id/avatar"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/text_size_tiny"
tools:text="09:09 John Smith"/>
+
+
{
-
- private final int pad;
-
- ForumAdapter(Context ctx) {
- super(ctx, android.R.layout.simple_expandable_list_item_1,
- new ArrayList());
- pad = LayoutUtils.getPadding(ctx);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ForumItem item = getItem(position);
- ForumPostHeader header = item.getHeader();
- Context ctx = getContext();
- Resources res = ctx.getResources();
-
- LinearLayout layout = new LinearLayout(ctx);
- layout.setOrientation(VERTICAL);
- layout.setGravity(CENTER_HORIZONTAL);
- if (!header.isRead())
- layout.setBackgroundColor(res.getColor(R.color.unread_background));
-
- LinearLayout headerLayout = new LinearLayout(ctx);
- headerLayout.setOrientation(HORIZONTAL);
- headerLayout.setGravity(CENTER_VERTICAL);
-
- AuthorView authorView = new AuthorView(ctx);
- authorView.setLayoutParams(WRAP_WRAP_1);
- authorView.setPadding(0, pad, pad, pad);
- Author author = header.getAuthor();
- if (author == null) {
- authorView.init(null, null, header.getAuthorStatus());
- } else {
- authorView.init(author.getName(), author.getId(),
- header.getAuthorStatus());
- }
- headerLayout.addView(authorView);
-
- TextView date = new TextView(ctx);
- date.setPadding(pad, pad, pad, pad);
- long timestamp = header.getTimestamp();
- date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
- headerLayout.addView(date);
- layout.addView(headerLayout);
-
- if (item.getBody() == null) {
- TextView ellipsis = new TextView(ctx);
- ellipsis.setPadding(pad, 0, pad, pad);
- ellipsis.setText("\u2026");
- layout.addView(ellipsis);
- } else if (header.getContentType().equals("text/plain")) {
- TextView text = new TextView(ctx);
- text.setPadding(pad, 0, pad, pad);
- text.setText(StringUtils.fromUtf8(item.getBody()));
- layout.addView(text);
- } else {
- ImageButton attachment = new ImageButton(ctx);
- attachment.setPadding(pad, 0, pad, pad);
- attachment.setImageResource(R.drawable.content_attachment);
- layout.addView(attachment);
- }
-
- return layout;
- }
-}
\ No newline at end of file
diff --git a/briar-android/src/org/briarproject/android/forum/ForumEntry.java b/briar-android/src/org/briarproject/android/forum/ForumEntry.java
index c3c668f7a..a24d94bbc 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumEntry.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumEntry.java
@@ -1,6 +1,7 @@
package org.briarproject.android.forum;
import org.briarproject.api.forum.ForumPostHeader;
+import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.sync.MessageId;
@@ -12,23 +13,25 @@ public class ForumEntry {
private final long timestamp;
private final String author;
private final AuthorId authorId;
+ private Status status;
private boolean isShowingDescendants = true;
private boolean isRead = true;
public ForumEntry(ForumPostHeader h, String text, int level) {
this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(),
- h.getAuthor().getId());
+ h.getAuthor().getId(), h.getAuthorStatus());
this.isRead = h.isRead();
}
public ForumEntry(MessageId messageId, String text, int level,
- long timestamp, String author, AuthorId authorId) {
+ long timestamp, String author, AuthorId authorId, Status status) {
this.messageId = messageId;
this.text = text;
this.level = level;
this.timestamp = timestamp;
this.author = author;
this.authorId = authorId;
+ this.status = status;
}
public String getText() {
@@ -51,6 +54,10 @@ public class ForumEntry {
return authorId;
}
+ public Status getStatus() {
+ return status;
+ }
+
public boolean isShowingDescendants() {
return isShowingDescendants;
}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java
index ec1470ed8..2aef2ae5e 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java
@@ -15,6 +15,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
+import static org.briarproject.api.identity.Author.Status.UNVERIFIED;
+
public class ForumTestControllerImpl implements ForumController {
private static final Logger LOG =
@@ -115,7 +117,7 @@ public class ForumTestControllerImpl implements ForumController {
forumEntries[e] =
new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]),
l[e], timestamp, AUTHORS[authorIndex],
- AUTHOR_ID[authorIndex]);
+ AUTHOR_ID[authorIndex], UNVERIFIED);
}
LOG.info("forum entries: " + forumEntries.length);
resultHandler.onResult(true);
diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java
deleted file mode 100644
index 4b11d51e8..000000000
--- a/briar-android/src/org/briarproject/android/util/AuthorView.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.briarproject.android.util;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.briarproject.R;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.identity.Author;
-import org.briarproject.api.identity.AuthorId;
-
-import javax.inject.Inject;
-
-import im.delight.android.identicons.IdenticonDrawable;
-
-public class AuthorView extends FrameLayout {
-
- private ImageView avatarView;
- private TextView nameView;
- private ImageView statusView;
-
- public AuthorView(Context ctx) {
- super(ctx);
-
- initViews();
- }
-
- public AuthorView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- initViews();
- }
-
- public AuthorView(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
-
- initViews();
- }
-
- private void initViews() {
- if (isInEditMode())
- return;
-
- View v = LayoutInflater.from(getContext()).inflate(
- R.layout.author_view, this, true);
-
- avatarView = (ImageView) v.findViewById(R.id.avatarView);
- nameView = (TextView) v.findViewById(R.id.nameView);
- statusView = (ImageView) v.findViewById(R.id.statusView);
- }
-
- public void init(String name, AuthorId id, Author.Status status) {
- if (name == null) {
- nameView.setText(R.string.anonymous);
- } else {
- nameView.setText(name);
- avatarView.setImageDrawable(
- new IdenticonDrawable(id.getBytes()));
- }
-
- switch(status) {
- case ANONYMOUS:
- statusView.setImageResource(R.drawable.identity_anonymous);
- break;
- case UNKNOWN:
- statusView.setImageResource(R.drawable.identity_unknown);
- break;
- case UNVERIFIED:
- statusView.setImageResource(R.drawable.identity_unverified);
- break;
- case VERIFIED:
- statusView.setImageResource(R.drawable.identity_verified);
- break;
- }
- }
-}
diff --git a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java
new file mode 100644
index 000000000..ce01bf301
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java
@@ -0,0 +1,44 @@
+package org.briarproject.android.util;
+
+import android.content.Context;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import org.briarproject.R;
+import org.briarproject.api.identity.Author.Status;
+
+public class TrustIndicatorView extends ImageView {
+
+ public TrustIndicatorView(Context context) {
+ super(context);
+ }
+
+ public TrustIndicatorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TrustIndicatorView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setTrustLevel(Status status) {
+ int res;
+ switch (status) {
+ case ANONYMOUS:
+ res = R.drawable.trust_indicator_anonymous;
+ break;
+ case UNVERIFIED:
+ res = R.drawable.trust_indicator_unverified;
+ break;
+ case VERIFIED:
+ res = R.drawable.trust_indicator_verified;
+ break;
+ default:
+ res = R.drawable.trust_indicator_unknown;
+ }
+ setImageDrawable(ContextCompat.getDrawable(getContext(), res));
+ }
+
+}
diff --git a/briar-api/build.gradle b/briar-api/build.gradle
index d38a8b520..479b38fdc 100644
--- a/briar-api/build.gradle
+++ b/briar-api/build.gradle
@@ -7,14 +7,16 @@ apply plugin: 'witness'
dependencies {
compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2'
+ compile 'org.jetbrains:annotations-java5:15.0'
}
dependencyVerification {
verify = [
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
- 'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
+ 'org.jetbrains:annotations-java5:c84e6e9947f802ec2183bdc415dd496df02a749cac92e805f697e60f628a1e24',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
+ 'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
]
}
diff --git a/briar-api/src/org/briarproject/api/blogs/Blog.java b/briar-api/src/org/briarproject/api/blogs/Blog.java
new file mode 100644
index 000000000..c97e42be2
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/Blog.java
@@ -0,0 +1,32 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.forum.Forum;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Group;
+import org.jetbrains.annotations.NotNull;
+
+public class Blog extends Forum {
+
+ @NotNull
+ private final String description;
+ @NotNull
+ private final Author author;
+
+ public Blog(@NotNull Group group, @NotNull String name,
+ @NotNull String description, @NotNull Author author) {
+ super(group, name, null);
+
+ this.description = description;
+ this.author = author;
+ }
+
+ @NotNull
+ public String getDescription() {
+ return description;
+ }
+
+ @NotNull
+ public Author getAuthor() {
+ return author;
+ }
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
new file mode 100644
index 000000000..60f06a641
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
@@ -0,0 +1,39 @@
+package org.briarproject.api.blogs;
+
+import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+
+public interface BlogConstants {
+
+ /** The maximum length of a blogs's name in UTF-8 bytes. */
+ int MAX_BLOG_TITLE_LENGTH = 100;
+
+ /** 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 length of a blog post's teaser in UTF-8 bytes. */
+ int MAX_BLOG_POST_TEASER_LENGTH = 240;
+
+ /** The maximum length of a blog post's body in bytes. */
+ int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
+
+ // Metadata keys
+ String KEY_DESCRIPTION = "description";
+ String KEY_TITLE = "title";
+ String KEY_TEASER = "teaser";
+ String KEY_HAS_BODY = "hasBody";
+ String KEY_TIMESTAMP = "timestamp";
+ String KEY_PARENT = "parent";
+ 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";
+
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java
new file mode 100644
index 000000000..8da9db192
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java
@@ -0,0 +1,17 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Group;
+import org.jetbrains.annotations.NotNull;
+
+public interface BlogFactory {
+
+ /** Creates a blog with the given name, description and author. */
+ Blog createBlog(@NotNull String name, @NotNull String description,
+ @NotNull Author author);
+
+ /** Parses a blog with the given Group and description */
+ Blog parseBlog(@NotNull Group g, @NotNull String description)
+ throws FormatException;
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
new file mode 100644
index 000000000..e01fa5efb
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
@@ -0,0 +1,48 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+public interface BlogManager {
+
+ /** Returns the unique ID of the blog client. */
+ ClientId getClientId();
+
+ /** Creates a new Blog. */
+ Blog addBlog(LocalAuthor localAuthor, String name, String description)
+ throws DbException;
+
+ /** Stores a local blog post. */
+ void addLocalPost(BlogPost p) throws DbException;
+
+ /** Returns the blog with the given ID. */
+ Blog getBlog(GroupId g) throws DbException;
+
+ /** Returns the blog with the given ID. */
+ Blog getBlog(Transaction txn, GroupId g) throws DbException;
+
+ /** Returns all blogs to which the localAuthor created. */
+ Collection getBlogs(LocalAuthor localAuthor) throws DbException;
+
+ /** Returns all blogs to which the user subscribes. */
+ Collection getBlogs() throws DbException;
+
+ /** Returns the body of the blog post with the given ID. */
+ @Nullable
+ byte[] getPostBody(MessageId m) throws DbException;
+
+ /** Returns the headers of all posts in the given blog. */
+ Collection getPostHeaders(GroupId g) throws DbException;
+
+ /** Marks a blog post as read or unread. */
+ void setReadFlag(MessageId m, boolean read) throws DbException;
+
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPost.java b/briar-api/src/org/briarproject/api/blogs/BlogPost.java
new file mode 100644
index 000000000..a6aa1167a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPost.java
@@ -0,0 +1,43 @@
+package org.briarproject.api.blogs;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import org.briarproject.api.forum.ForumPost;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+
+public class BlogPost extends ForumPost {
+
+ @Nullable
+ private final String title;
+ @NotNull
+ private final String teaser;
+ private final boolean hasBody;
+
+ public BlogPost(@Nullable String title, @NotNull String teaser,
+ boolean hasBody, @NotNull Message message,
+ @Nullable MessageId parent, @NotNull Author author,
+ @NotNull String contentType) {
+ super(message, parent, author, contentType);
+
+ this.title = title;
+ this.teaser = teaser;
+ this.hasBody = hasBody;
+ }
+
+ @Nullable
+ public String getTitle() {
+ return title;
+ }
+
+ @NotNull
+ public String getTeaser() {
+ return teaser;
+ }
+
+ public boolean hasBody() {
+ return hasBody;
+ }
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java
new file mode 100644
index 000000000..2565bfb0e
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java
@@ -0,0 +1,19 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.security.GeneralSecurityException;
+
+public interface BlogPostFactory {
+
+ BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
+ @NotNull String teaser, long timestamp, @Nullable MessageId parent,
+ @NotNull LocalAuthor author, @NotNull String contentType,
+ @Nullable byte[] body)
+ throws FormatException, GeneralSecurityException;
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
new file mode 100644
index 000000000..24d421c13
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
@@ -0,0 +1,43 @@
+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.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class BlogPostHeader extends PostHeader {
+
+ @Nullable
+ private final String title;
+ @NotNull
+ private final String teaser;
+ private final boolean hasBody;
+
+ public BlogPostHeader(@Nullable String title, @NotNull String teaser,
+ boolean hasBody, @NotNull MessageId id,
+ @Nullable MessageId parentId, long timestamp,
+ @NotNull Author author, @NotNull Status authorStatus,
+ @NotNull String contentType, boolean read) {
+ super(id, parentId, timestamp, author, authorStatus, contentType, read);
+
+ this.title = title;
+ this.teaser = teaser;
+ this.hasBody = hasBody;
+ }
+
+ @Nullable
+ public String getTitle() {
+ return title;
+ }
+
+ @NotNull
+ public String getTeaser() {
+ return teaser;
+ }
+
+ public boolean hasBody() {
+ return hasBody;
+ }
+}
diff --git a/briar-api/src/org/briarproject/api/clients/PostHeader.java b/briar-api/src/org/briarproject/api/clients/PostHeader.java
new file mode 100644
index 000000000..33f0d2877
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/clients/PostHeader.java
@@ -0,0 +1,55 @@
+package org.briarproject.api.clients;
+
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.MessageId;
+
+public abstract class PostHeader {
+
+ private final MessageId id;
+ private final MessageId parentId;
+ private final long timestamp;
+ private final Author author;
+ private final Author.Status authorStatus;
+ private final String contentType;
+ private final boolean read;
+
+ public PostHeader(MessageId id, MessageId parentId, long timestamp,
+ Author author, Author.Status authorStatus, String contentType,
+ boolean read) {
+ this.id = id;
+ this.parentId = parentId;
+ this.timestamp = timestamp;
+ this.author = author;
+ this.authorStatus = authorStatus;
+ this.contentType = contentType;
+ this.read = read;
+ }
+
+ public MessageId getId() {
+ return id;
+ }
+
+ public Author getAuthor() {
+ return author;
+ }
+
+ public Author.Status getAuthorStatus() {
+ return authorStatus;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public boolean isRead() {
+ return read;
+ }
+
+ public MessageId getParentId() {
+ return parentId;
+ }
+}
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
index 407793905..c25a9e251 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
@@ -1,56 +1,17 @@
package org.briarproject.api.forum;
import org.briarproject.api.clients.MessageTree;
+import org.briarproject.api.clients.PostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.MessageId;
-public class ForumPostHeader implements MessageTree.MessageNode {
-
- private final MessageId id;
- private final MessageId parentId;
- private final long timestamp;
- private final Author author;
- private final Author.Status authorStatus;
- private final String contentType;
- private final boolean read;
+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) {
- this.id = id;
- this.parentId = parentId;
- this.timestamp = timestamp;
- this.author = author;
- this.authorStatus = authorStatus;
- this.contentType = contentType;
- this.read = read;
+ super(id, parentId, timestamp, author, authorStatus, contentType, read);
}
- public MessageId getId() {
- return id;
- }
-
- public Author getAuthor() {
- return author;
- }
-
- public Author.Status getAuthorStatus() {
- return authorStatus;
- }
-
- public String getContentType() {
- return contentType;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public boolean isRead() {
- return read;
- }
-
- public MessageId getParentId() {
- return parentId;
- }
}
diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
index f2a4db2d9..e5420a043 100644
--- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java
+++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
@@ -2,6 +2,7 @@ package org.briarproject.api.identity;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.Author.Status;
import java.util.Collection;
@@ -25,6 +26,9 @@ public interface IdentityManager {
/** Removes a local pseudonym and all associated state. */
void removeLocalAuthor(AuthorId a) throws DbException;
+ /** Returns the trust-level status of the author */
+ Status getAuthorStatus(AuthorId a) throws DbException;
+
interface AddIdentityHook {
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
}
diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java
index 174ee9c57..6847f4612 100644
--- a/briar-core/src/org/briarproject/CoreEagerSingletons.java
+++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java
@@ -1,5 +1,6 @@
package org.briarproject;
+import org.briarproject.blogs.BlogsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseExecutorModule;
@@ -15,6 +16,8 @@ import org.briarproject.transport.TransportModule;
public interface CoreEagerSingletons {
+ void inject(BlogsModule.EagerSingletons init);
+
void inject(ContactModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init);
diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java
index 238c859e7..6a909b3f6 100644
--- a/briar-core/src/org/briarproject/CoreModule.java
+++ b/briar-core/src/org/briarproject/CoreModule.java
@@ -1,5 +1,6 @@
package org.briarproject;
+import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
@@ -26,6 +27,7 @@ import org.briarproject.transport.TransportModule;
import dagger.Module;
@Module(includes = {
+ BlogsModule.class,
ClientsModule.class,
ContactModule.class,
CryptoModule.class,
@@ -52,6 +54,7 @@ import dagger.Module;
public class CoreModule {
public static void initEagerSingletons(CoreEagerSingletons c) {
+ c.inject(new BlogsModule.EagerSingletons());
c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());
diff --git a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
new file mode 100644
index 000000000..b04401f5d
--- /dev/null
+++ b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
@@ -0,0 +1,61 @@
+package org.briarproject.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogFactory;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupFactory;
+import org.jetbrains.annotations.NotNull;
+
+import javax.inject.Inject;
+
+class BlogFactoryImpl implements BlogFactory {
+
+ private final GroupFactory groupFactory;
+ private final AuthorFactory authorFactory;
+ private final ClientHelper clientHelper;
+
+ @Inject
+ BlogFactoryImpl(GroupFactory groupFactory, AuthorFactory authorFactory,
+ ClientHelper clientHelper) {
+
+ this.groupFactory = groupFactory;
+ this.authorFactory = authorFactory;
+ this.clientHelper = clientHelper;
+ }
+
+ @Override
+ public Blog createBlog(@NotNull String name, @NotNull String description,
+ @NotNull Author author) {
+ try {
+ BdfList blog = BdfList.of(
+ name,
+ author.getName(),
+ author.getPublicKey()
+ );
+ byte[] descriptor = clientHelper.toByteArray(blog);
+ Group g = groupFactory
+ .createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
+ return new Blog(g, name, description, author);
+ } catch (FormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Blog parseBlog(@NotNull Group g, @NotNull String description)
+ throws FormatException {
+
+ byte[] descriptor = g.getDescriptor();
+ // Blog Name, Author Name, Public Key
+ BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length);
+ Author a =
+ authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
+ return new Blog(g, blog.getString(0), description, a);
+ }
+
+}
diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
new file mode 100644
index 000000000..eabfc251e
--- /dev/null
+++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
@@ -0,0 +1,260 @@
+package org.briarproject.blogs;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogFactory;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogPost;
+import org.briarproject.api.blogs.BlogPostHeader;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfEntry;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.ClientId;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.util.StringUtils;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+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_HAS_BODY;
+import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
+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_TEASER;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
+
+class BlogManagerImpl implements BlogManager {
+
+ private static final Logger LOG =
+ Logger.getLogger(BlogManagerImpl.class.getName());
+
+ static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
+ "dafbe56f0c8971365cea4bb5f08ec9a6" +
+ "1d686e058b943997b6ff259ba423f613"));
+
+ private final DatabaseComponent db;
+ private final IdentityManager identityManager;
+ private final ClientHelper clientHelper;
+ private final BlogFactory blogFactory;
+
+ @Inject
+ BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
+ ClientHelper clientHelper, BlogFactory blogFactory) {
+
+ this.db = db;
+ this.identityManager = identityManager;
+ this.clientHelper = clientHelper;
+ this.blogFactory = blogFactory;
+ }
+
+ @Override
+ public ClientId getClientId() {
+ return CLIENT_ID;
+ }
+
+ @Override
+ public Blog addBlog(LocalAuthor localAuthor, String name,
+ String description) throws DbException {
+
+ Blog b = blogFactory
+ .createBlog(name, description, localAuthor);
+ BdfDictionary metadata = BdfDictionary.of(
+ new BdfEntry(KEY_DESCRIPTION, b.getDescription())
+ );
+
+ Transaction txn = db.startTransaction(false);
+ try {
+ db.addGroup(txn, b.getGroup());
+ clientHelper.mergeGroupMetadata(txn, b.getId(), metadata);
+ txn.setComplete();
+ } catch (FormatException e) {
+ throw new DbException(e);
+ } finally {
+ db.endTransaction(txn);
+ }
+ return b;
+ }
+
+ @Override
+ public void addLocalPost(BlogPost p) throws DbException {
+ try {
+ BdfDictionary meta = new BdfDictionary();
+ if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
+ meta.put(KEY_TEASER, p.getTeaser());
+ meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
+ meta.put(KEY_HAS_BODY, p.hasBody());
+ if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent());
+
+ 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_READ, true);
+ clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
+ } catch (FormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Blog getBlog(GroupId g) throws DbException {
+ Blog blog;
+ Transaction txn = db.startTransaction(true);
+ try {
+ blog = getBlog(txn, g);
+ txn.setComplete();
+ } finally {
+ db.endTransaction(txn);
+ }
+ return blog;
+ }
+
+ @Override
+ public Blog getBlog(Transaction txn, GroupId g) throws DbException {
+ try {
+ Group group = db.getGroup(txn, g);
+ String description = getBlogDescription(txn, g);
+ return blogFactory.parseBlog(group, description);
+ } catch (FormatException e) {
+ throw new DbException(e);
+ }
+ }
+
+ @Override
+ public Collection getBlogs(LocalAuthor localAuthor)
+ throws DbException {
+
+ Collection allBlogs = getBlogs();
+ List blogs = new ArrayList();
+ for (Blog b : allBlogs) {
+ if (b.getAuthor().equals(localAuthor)) {
+ blogs.add(b);
+ }
+ }
+ return Collections.unmodifiableList(blogs);
+ }
+
+ @Override
+ public Collection getBlogs() throws DbException {
+ try {
+ List blogs = new ArrayList();
+ Collection groups;
+ Transaction txn = db.startTransaction(true);
+ try {
+ groups = db.getGroups(txn, CLIENT_ID);
+ for (Group g : groups) {
+ String description = getBlogDescription(txn, g.getId());
+ blogs.add(blogFactory.parseBlog(g, description));
+ }
+ txn.setComplete();
+ } finally {
+ db.endTransaction(txn);
+ }
+ return Collections.unmodifiableList(blogs);
+ } catch (FormatException e) {
+ throw new DbException(e);
+ }
+ }
+
+ @Override
+ @Nullable
+ public byte[] getPostBody(MessageId m) throws DbException {
+ try {
+ // content, signature
+ // content: parent, contentType, title, teaser, body, attachments
+ BdfList message = clientHelper.getMessageAsList(m);
+ BdfList content = message.getList(0);
+ return content.getRaw(4);
+ } catch (FormatException e) {
+ throw new DbException(e);
+ }
+ }
+
+ @Override
+ public Collection getPostHeaders(GroupId g)
+ throws DbException {
+
+ Map metadata;
+ try {
+ metadata = clientHelper.getMessageMetadataAsDictionary(g);
+ } catch (FormatException e) {
+ throw new DbException(e);
+ }
+ Collection headers = new ArrayList();
+ for (Entry entry : metadata.entrySet()) {
+ try {
+ BdfDictionary meta = entry.getValue();
+ String title = meta.getOptionalString(KEY_TITLE);
+ String teaser = meta.getString(KEY_TEASER);
+ boolean hasBody = meta.getBoolean(KEY_HAS_BODY);
+ long timestamp = meta.getLong(KEY_TIMESTAMP);
+ MessageId parentId = null;
+ if (meta.containsKey(KEY_PARENT))
+ parentId = new MessageId(meta.getRaw(KEY_PARENT));
+
+ BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
+ AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
+ String name = d.getString(KEY_AUTHOR_NAME);
+ byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
+ Author author = new Author(authorId, name, publicKey);
+ Status authorStatus = identityManager.getAuthorStatus(authorId);
+
+ String contentType = meta.getString(KEY_CONTENT_TYPE);
+ boolean read = meta.getBoolean(KEY_READ);
+ headers.add(new BlogPostHeader(title, teaser, hasBody,
+ entry.getKey(), parentId, timestamp, author,
+ authorStatus, contentType, read));
+ } catch (FormatException e) {
+ throw new DbException(e);
+ }
+ }
+ return headers;
+ }
+
+ @Override
+ public void setReadFlag(MessageId m, boolean read) throws DbException {
+ try {
+ BdfDictionary meta = new BdfDictionary();
+ meta.put(KEY_READ, read);
+ clientHelper.mergeMessageMetadata(m, meta);
+ } catch (FormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getBlogDescription(Transaction txn, GroupId g)
+ throws DbException, FormatException {
+ BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g);
+ return d.getString(KEY_DESCRIPTION);
+ }
+
+}
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
new file mode 100644
index 000000000..e9ced2d3f
--- /dev/null
+++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
@@ -0,0 +1,78 @@
+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.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.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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_TEASER_LENGTH;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
+import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
+
+class BlogPostFactoryImpl implements BlogPostFactory {
+
+ private final CryptoComponent crypto;
+ private final ClientHelper clientHelper;
+
+ @Inject
+ BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
+ this.crypto = crypto;
+ this.clientHelper = clientHelper;
+ }
+
+ @Override
+ public BlogPost createBlogPost(@NotNull GroupId groupId,
+ @Nullable String title, @NotNull String teaser, long timestamp,
+ @Nullable MessageId parent, @NotNull LocalAuthor author,
+ @NotNull String contentType, @Nullable byte[] 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(teaser).length > MAX_BLOG_POST_TEASER_LENGTH)
+ throw new IllegalArgumentException();
+ if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
+ throw new IllegalArgumentException();
+ if (body != null && body.length > MAX_BLOG_POST_BODY_LENGTH)
+ throw new IllegalArgumentException();
+
+ // Serialise the data to be signed
+ BdfList content =
+ BdfList.of(parent, contentType, title, teaser, body, null);
+ BdfList signed = BdfList.of(groupId, timestamp, content);
+
+ // 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();
+
+ // Serialise the signed message
+ BdfList message = BdfList.of(content, sig);
+ Message m = clientHelper.createMessage(groupId, timestamp, message);
+ return new BlogPost(title, teaser, body != null, m, parent, author,
+ contentType);
+ }
+}
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
new file mode 100644
index 000000000..bacf02dff
--- /dev/null
+++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
@@ -0,0 +1,125 @@
+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.clients.BdfMessageContext;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.KeyParser;
+import org.briarproject.api.crypto.PublicKey;
+import org.briarproject.api.crypto.Signature;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.InvalidMessageException;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
+import org.briarproject.api.system.Clock;
+import org.briarproject.clients.BdfMessageValidator;
+
+import java.security.GeneralSecurityException;
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
+import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
+import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
+import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TEASER;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+
+class BlogPostValidator extends BdfMessageValidator {
+
+ private final CryptoComponent crypto;
+ private final BlogFactory blogFactory;
+
+ BlogPostValidator(CryptoComponent crypto, BlogFactory blogFactory,
+ ClientHelper clientHelper, MetadataEncoder metadataEncoder,
+ Clock clock) {
+ super(clientHelper, metadataEncoder, clock);
+
+ this.crypto = crypto;
+ this.blogFactory = blogFactory;
+ }
+
+ @Override
+ protected BdfMessageContext validateMessage(Message m, Group g,
+ BdfList body) throws InvalidMessageException, FormatException {
+
+ // Content, Signature
+ checkSize(body, 2);
+ BdfList content = body.getList(0);
+
+ // Content: Parent ID, content type, title (optional), teaser,
+ // post body (optional), attachments (optional)
+ checkSize(body, 6);
+ // Parent ID is optional
+ byte[] parent = content.getOptionalRaw(0);
+ checkLength(parent, UniqueId.LENGTH);
+ // Content type
+ String contentType = content.getString(1);
+ checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
+ // Blog post title is optional
+ String title = content.getOptionalString(2);
+ checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH);
+ // Blog teaser
+ String teaser = content.getString(3);
+ // TODO make sure that there is only text in the teaser
+ checkLength(contentType, 0, MAX_BLOG_POST_TEASER_LENGTH);
+ // Blog post body is optional
+ byte[] postBody = content.getOptionalRaw(4);
+ checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
+ // Attachments
+ BdfDictionary attachments = content.getOptionalDictionary(5);
+ // TODO handle attachments somehow
+
+ // Signature
+ byte[] sig = body.getRaw(1);
+ checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
+ // Verify the signature
+ try {
+ // Get the blog author
+ Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
+ Author a = b.getAuthor();
+ // Parse the public key
+ KeyParser keyParser = crypto.getSignatureKeyParser();
+ PublicKey key = keyParser.parsePublicKey(a.getPublicKey());
+ // Serialise the data to be signed
+ BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
+ // Verify the signature
+ Signature signature = crypto.getSignature();
+ signature.initVerify(key);
+ signature.update(clientHelper.toByteArray(signed));
+ if (!signature.verify(sig)) {
+ throw new InvalidMessageException("Invalid signature");
+ }
+ } catch (GeneralSecurityException e) {
+ throw new InvalidMessageException("Invalid public key");
+ }
+
+ // Return the metadata and dependencies
+ BdfDictionary meta = new BdfDictionary();
+ Collection dependencies = null;
+ if (title != null) meta.put(KEY_TITLE, title);
+ meta.put(KEY_TEASER, teaser);
+ meta.put(KEY_HAS_BODY, postBody != null);
+ meta.put(KEY_TIMESTAMP, m.getTimestamp());
+ if (parent != null) {
+ meta.put(KEY_PARENT, parent);
+ dependencies = Collections.singletonList(new MessageId(parent));
+ }
+ meta.put(KEY_CONTENT_TYPE, contentType);
+ meta.put(KEY_READ, false);
+ return new BdfMessageContext(meta, dependencies);
+ }
+}
diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java
new file mode 100644
index 000000000..4cf7894d1
--- /dev/null
+++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java
@@ -0,0 +1,63 @@
+package org.briarproject.blogs;
+
+import org.briarproject.api.blogs.BlogFactory;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogPostFactory;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.sync.GroupFactory;
+import org.briarproject.api.sync.ValidationManager;
+import org.briarproject.api.system.Clock;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class BlogsModule {
+
+ public static class EagerSingletons {
+ @Inject
+ BlogPostValidator blogPostValidator;
+ }
+
+ @Provides
+ @Singleton
+ BlogManager provideBlogManager(BlogManagerImpl blogManager) {
+ return blogManager;
+ }
+
+ @Provides
+ BlogPostFactory provideBlogPostFactory(CryptoComponent crypto,
+ ClientHelper clientHelper) {
+ return new BlogPostFactoryImpl(crypto, clientHelper);
+ }
+
+ @Provides
+ BlogFactory provideBlogFactory(GroupFactory groupFactory,
+ AuthorFactory authorFactory, ClientHelper clientHelper) {
+ return new BlogFactoryImpl(groupFactory, authorFactory, clientHelper);
+ }
+
+ @Provides
+ @Singleton
+ BlogPostValidator provideBlogPostValidator(
+ ValidationManager validationManager, CryptoComponent crypto,
+ BlogFactory blogFactory, ClientHelper clientHelper,
+ MetadataEncoder metadataEncoder, Clock clock) {
+
+ BlogPostValidator validator = new BlogPostValidator(crypto,
+ blogFactory, clientHelper, metadataEncoder, clock);
+ validationManager.registerMessageValidator(
+ BlogManagerImpl.CLIENT_ID, validator);
+
+ return validator;
+ }
+
+}
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 29bf817aa..36f8357ba 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -2,7 +2,6 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
@@ -14,8 +13,9 @@ import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId;
-import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
@@ -25,11 +25,9 @@ import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
@@ -45,8 +43,6 @@ import static org.briarproject.api.forum.ForumConstants.KEY_PUBLIC_NAME;
import static org.briarproject.api.forum.ForumConstants.KEY_READ;
import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP;
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
-import static org.briarproject.api.identity.Author.Status.UNKNOWN;
-import static org.briarproject.api.identity.Author.Status.VERIFIED;
class ForumManagerImpl implements ForumManager {
@@ -58,15 +54,17 @@ class ForumManagerImpl implements ForumManager {
+ "795af837abbf8c16d750b3c2ccc186ea"));
private final DatabaseComponent db;
+ private final IdentityManager identityManager;
private final ClientHelper clientHelper;
private final ForumFactory forumFactory;
private final List removeHooks;
@Inject
- ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
- ForumFactory forumFactory) {
+ ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
+ ClientHelper clientHelper, ForumFactory forumFactory) {
this.db = db;
+ this.identityManager = identityManager;
this.clientHelper = clientHelper;
this.forumFactory = forumFactory;
removeHooks = new CopyOnWriteArrayList();
@@ -183,24 +181,12 @@ class ForumManagerImpl implements ForumManager {
@Override
public Collection getPostHeaders(GroupId g)
throws DbException {
- Set localAuthorIds = new HashSet();
- Set contactAuthorIds = new HashSet();
+
Map metadata;
- Transaction txn = db.startTransaction(true);
try {
- // Load the IDs of the user's identities
- for (LocalAuthor a : db.getLocalAuthors(txn))
- localAuthorIds.add(a.getId());
- // Load the IDs of contacts' identities
- for (Contact c : db.getContacts(txn))
- contactAuthorIds.add(c.getAuthor().getId());
- // Load the metadata
- metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
- txn.setComplete();
+ metadata = clientHelper.getMessageMetadataAsDictionary(g);
} catch (FormatException e) {
throw new DbException(e);
- } finally {
- db.endTransaction(txn);
}
// Parse the metadata
Collection headers = new ArrayList();
@@ -209,7 +195,7 @@ class ForumManagerImpl implements ForumManager {
BdfDictionary meta = entry.getValue();
long timestamp = meta.getLong(KEY_TIMESTAMP);
Author author = null;
- Author.Status authorStatus = ANONYMOUS;
+ Status authorStatus = ANONYMOUS;
MessageId parentId = null;
if (meta.containsKey(KEY_PARENT))
parentId = new MessageId(meta.getRaw(KEY_PARENT));
@@ -219,11 +205,8 @@ class ForumManagerImpl implements ForumManager {
String name = d1.getString(KEY_NAME);
byte[] publicKey = d1.getRaw(KEY_PUBLIC_NAME);
author = new Author(authorId, name, publicKey);
- if (localAuthorIds.contains(authorId))
- authorStatus = VERIFIED;
- else if (contactAuthorIds.contains(authorId))
- authorStatus = VERIFIED;
- else authorStatus = UNKNOWN;
+ authorStatus =
+ identityManager.getAuthorStatus(author.getId());
}
String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ);
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index e7494850e..59f56af2a 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -11,6 +11,7 @@ import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.ValidationManager;
@@ -38,9 +39,8 @@ public class ForumModule {
@Provides
@Singleton
- ForumManager provideForumManager(DatabaseComponent db,
- ClientHelper clientHelper, ForumFactory forumFactory) {
- return new ForumManagerImpl(db, clientHelper, forumFactory);
+ ForumManager provideForumManager(ForumManagerImpl forumManager) {
+ return forumManager;
}
@Provides
@@ -88,7 +88,7 @@ public class ForumModule {
LifecycleManager lifecycleManager,
ContactManager contactManager,
MessageQueueManager messageQueueManager,
- ForumManager forumManager, ForumFactory forumFactory,
+ ForumManager forumManager,
ForumSharingManagerImpl forumSharingManager) {
lifecycleManager.registerClient(forumSharingManager);
diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
index b3d7a1f4d..e04e20602 100644
--- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
+++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
@@ -1,18 +1,25 @@
package org.briarproject.identity;
+import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
+import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
+import static org.briarproject.api.identity.Author.Status.UNKNOWN;
+import static org.briarproject.api.identity.Author.Status.VERIFIED;
+
class IdentityManagerImpl implements IdentityManager {
private final DatabaseComponent db;
private final List addHooks;
@@ -87,4 +94,22 @@ class IdentityManagerImpl implements IdentityManager {
db.endTransaction(txn);
}
}
+
+ @Override
+ public Status getAuthorStatus(AuthorId authorId) throws DbException {
+ Transaction txn = db.startTransaction(false);
+ try {
+ // Compare to the IDs of the user's identities
+ for (LocalAuthor a : db.getLocalAuthors(txn))
+ if (a.getId().equals(authorId)) return VERIFIED;
+ // Compare to the IDs of contacts' identities
+ for (Contact c : db.getContacts(txn))
+ if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
+
+ // TODO also handle UNVERIFIED when #261 is implemented
+ return UNKNOWN;
+ } finally {
+ db.endTransaction(txn);
+ }
+ }
}