diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java index e955ea3a477061a32928660fceb4e4023527193f..565f9f1f8d48a423320bba341d880c321313a532 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java @@ -8,7 +8,7 @@ abstract class ConversationIntroductionItem extends ConversationItem { private boolean answered; public ConversationIntroductionItem(IntroductionRequest ir) { - super(ir.getMessageId(), ir.getTime()); + super(ir.getMessageId(), ir.getTimestamp()); this.ir = ir; this.answered = ir.wasAnswered(); diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java index 76eb803a35f96f02c9df891af873d6605f08f2e4..2fc96b6ab7e54857c3bb53f212b45c50c18fda67 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationItem.java @@ -69,7 +69,7 @@ public abstract class ConversationItem { ir.getName()); } return new ConversationNoticeOutItem(ir.getMessageId(), text, - ir.getTime(), ir.isSent(), ir.isSeen()); + ir.getTimestamp(), ir.isSent(), ir.isSeen()); } else { String text; if (ir.wasAccepted()) { @@ -88,7 +88,7 @@ public abstract class ConversationItem { } } return new ConversationNoticeInItem(ir.getMessageId(), text, - ir.getTime(), ir.isRead()); + ir.getTimestamp(), ir.isRead()); } } @@ -98,9 +98,9 @@ public abstract class ConversationItem { public static ConversationItem from(IntroductionMessage im) { if (im.isLocal()) return new ConversationNoticeOutItem(im.getMessageId(), "", - im.getTime(), false, false); - return new ConversationNoticeInItem(im.getMessageId(), "", im.getTime(), - im.isRead()); + im.getTimestamp(), false, false); + return new ConversationNoticeInItem(im.getMessageId(), "", + im.getTimestamp(), im.isRead()); } protected interface OutgoingItem { diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java index eb4ade9abee989407225653dd7b97b6ad6b55c12..f685e0d0670c8e169aff0a2e89de7b9b37d6db97 100644 --- a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java +++ b/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java @@ -171,7 +171,6 @@ implements EventListener, OnItemClickListener { public void run() { try { forumManager.addForum(f); - forumSharingManager.setSharedWith(f.getId(), shared); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); diff --git a/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java b/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java index cccecbc7ae4c97a293edb50ab1d908f18870d8d8..ccb0b365d907f65b419929850c9b1d7ea369279e 100644 --- a/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java @@ -91,7 +91,6 @@ public class ShareForumActivity extends BriarActivity implements onBackPressed(); return true; case R.id.action_share_forum: - storeVisibility(); return true; default: return super.onOptionsItemSelected(item); @@ -140,26 +139,6 @@ public class ShareForumActivity extends BriarActivity implements }); } - private void storeVisibility() { - runOnDbThread(new Runnable() { - public void run() { - try { - long now = System.currentTimeMillis(); - Collection<ContactId> selected = - adapter.getSelectedContactIds(); - forumSharingManager.setSharedWith(groupId, selected); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Update took " + duration + " ms"); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - finishOnUiThread(); - } - }); - } - @Override public void onItemClick(View view, ContactListItem item) { ((SelectableContactListItem) item).toggleSelected(); diff --git a/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..1d8e7b5f7b61a1a88076277e48fb3b52cc34de13 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java @@ -0,0 +1,24 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.forum.Forum; +import org.briarproject.api.introduction.IntroductionRequest; + +public class ForumInvitationReceivedEvent extends Event { + + private final Forum forum; + private final ContactId contactId; + + public ForumInvitationReceivedEvent(Forum forum, ContactId contactId) { + this.forum = forum; + this.contactId = contactId; + } + + public Forum getForum() { + return forum; + } + + public ContactId getContactId() { + return contactId; + } +} diff --git a/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java b/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..1e799240386966ff59c73037c97e6cfaaaeb2e63 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/ForumInvitationResponseReceivedEvent.java @@ -0,0 +1,24 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; + +public class ForumInvitationResponseReceivedEvent extends Event { + + private final String forumName; + private final ContactId contactId; + + public ForumInvitationResponseReceivedEvent(String forumName, + ContactId contactId) { + + this.forumName = forumName; + this.contactId = contactId; + } + + public String getForumName() { + return forumName; + } + + public ContactId getContactId() { + return contactId; + } +} diff --git a/briar-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java index 035a06cd1ef800f91b1789fa3ae3f51da0c5f457..dcdee0132b9d1d807ca402c12e5972e3cf85ce09 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumConstants.java +++ b/briar-api/src/org/briarproject/api/forum/ForumConstants.java @@ -15,4 +15,38 @@ public interface ForumConstants { /** The maximum length of a forum post's body in bytes. */ int MAX_FORUM_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + + /* Forum Sharing Constants */ + String CONTACT_ID = "contactId"; + String GROUP_ID = "groupId"; + String TO_BE_SHARED_BY_US = "toBeSharedByUs"; + String SHARED_BY_US = "sharedByUs"; + String SHARED_WITH_US = "sharedWithUs"; + String TYPE = "type"; + String SESSION_ID = "sessionId"; + String STORAGE_ID = "storageId"; + String STATE = "state"; + String LOCAL = "local"; + String TIME = "time"; + String READ = "read"; + String IS_SHARER = "isSharer"; + String FORUM_ID = "forumId"; + String FORUM_NAME = "forumName"; + String FORUM_SALT = "forumSalt"; + String INVITATION_MSG = "invitationMsg"; + int SHARE_MSG_TYPE_INVITATION = 1; + int SHARE_MSG_TYPE_ACCEPT = 2; + int SHARE_MSG_TYPE_DECLINE = 3; + int SHARE_MSG_TYPE_LEAVE = 4; + int SHARE_MSG_TYPE_ABORT = 5; + String TASK = "task"; + int TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US = 0; + int TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US = 1; + int TASK_ADD_SHARED_FORUM = 2; + int TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US = 3; + int TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US = 4; + int TASK_SHARE_FORUM = 5; + int TASK_UNSHARE_FORUM_SHARED_BY_US = 6; + int TASK_UNSHARE_FORUM_SHARED_WITH_US = 7; + } diff --git a/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java b/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..b153c27f11cb996d5b48f3ad3ebb4aa47e17475d --- /dev/null +++ b/briar-api/src/org/briarproject/api/forum/ForumInvitationMessage.java @@ -0,0 +1,48 @@ +package org.briarproject.api.forum; + +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.messaging.BaseMessage; +import org.briarproject.api.sync.MessageId; + +public class ForumInvitationMessage extends BaseMessage { + + private final SessionId sessionId; + private final ContactId contactId; + private final String forumName, message; + private final boolean available; + + public ForumInvitationMessage(MessageId id, SessionId sessionId, + ContactId contactId, String forumName, String message, + boolean available, long time, boolean local, boolean sent, + boolean seen, boolean read) { + + super(id, time, local, read, sent, seen); + this.sessionId = sessionId; + this.contactId = contactId; + this.forumName = forumName; + this.message = message; + this.available = available; + } + + public SessionId getSessionId() { + return sessionId; + } + + public ContactId getContactId() { + return contactId; + } + + public String getForumName() { + return forumName; + } + + public String getMessage() { + return message; + } + + public boolean isAvailable() { + return available; + } + +} diff --git a/briar-api/src/org/briarproject/api/forum/ForumManager.java b/briar-api/src/org/briarproject/api/forum/ForumManager.java index bd5657b01bb8f6d262c036ffa676ce6f919c6f83..7c619294bea1b0777b355ab1f93af06433154190 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumManager.java @@ -31,6 +31,9 @@ public interface ForumManager { /** Returns the forum with the given ID. */ Forum getForum(GroupId g) throws DbException; + /** Returns the forum with the given ID. */ + Forum getForum(Transaction txn, GroupId g) throws DbException; + /** Returns all forums to which the user subscribes. */ Collection<Forum> getForums() throws DbException; diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java index e3e3b1191cca67bad368ab612018add384775ff5..9f4cc87b6620aebb593a32f3f2e9e3655207aa9a 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java @@ -1,5 +1,6 @@ package org.briarproject.api.forum; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; @@ -13,20 +14,35 @@ public interface ForumSharingManager { /** Returns the unique ID of the forum sharing client. */ ClientId getClientId(); + /** + * Sends an invitation to share the given forum with the given contact + * and sends an optional message along with it. + */ + void sendForumInvitation(GroupId groupId, ContactId contactId, + String message) throws DbException; + + /** + * Responds to a pending forum invitation + */ + void respondToInvitation(Forum f, boolean accept) throws DbException; + + /** + * Returns all forum sharing messages sent by the Contact + * identified by contactId. + */ + Collection<ForumInvitationMessage> getForumInvitationMessages( + ContactId contactId) throws DbException; + /** Returns all forums to which the user could subscribe. */ Collection<Forum> getAvailableForums() throws DbException; - /** Returns all contacts who are sharing the given forum with the user. */ + /** Returns all contacts who are sharing the given forum with us. */ Collection<Contact> getSharedBy(GroupId g) throws DbException; /** Returns the IDs of all contacts with whom the given forum is shared. */ Collection<ContactId> getSharedWith(GroupId g) throws DbException; - /** - * Shares a forum with the given contacts and unshares it with any other - * contacts. - */ - void setSharedWith(GroupId g, Collection<ContactId> shared) - throws DbException; + /** Returns true if the forum not already shared and no invitation is open */ + boolean canBeShared(GroupId g, Contact c) throws DbException; } diff --git a/briar-api/src/org/briarproject/api/forum/InviteeAction.java b/briar-api/src/org/briarproject/api/forum/InviteeAction.java new file mode 100644 index 0000000000000000000000000000000000000000..212f0861ce661de2e69b18adeb8a52ba9aa14af5 --- /dev/null +++ b/briar-api/src/org/briarproject/api/forum/InviteeAction.java @@ -0,0 +1,34 @@ +package org.briarproject.api.forum; + +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; + +public enum InviteeAction { + + LOCAL_ACCEPT, + LOCAL_DECLINE, + LOCAL_LEAVE, + LOCAL_ABORT, + REMOTE_INVITATION, + REMOTE_LEAVE, + REMOTE_ABORT; + + public static InviteeAction getLocal(long type) { + if (type == SHARE_MSG_TYPE_ACCEPT) return LOCAL_ACCEPT; + if (type == SHARE_MSG_TYPE_DECLINE) return LOCAL_DECLINE; + if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE; + if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT; + return null; + } + + public static InviteeAction getRemote(long type) { + if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION; + if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE; + if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT; + return null; + } + +} diff --git a/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java b/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java new file mode 100644 index 0000000000000000000000000000000000000000..35d6a880af0b9bbb41c3ffd2ede3122d9b7fb297 --- /dev/null +++ b/briar-api/src/org/briarproject/api/forum/InviteeProtocolState.java @@ -0,0 +1,62 @@ +package org.briarproject.api.forum; + +import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT; +import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE; +import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE; +import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION; +import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE; + +public enum InviteeProtocolState { + + ERROR(0), + AWAIT_INVITATION(1) { + @Override + public InviteeProtocolState next(InviteeAction a) { + if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE; + return ERROR; + } + }, + AWAIT_LOCAL_RESPONSE(2) { + @Override + public InviteeProtocolState next(InviteeAction a) { + if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED; + if (a == REMOTE_LEAVE) return LEFT; + return ERROR; + } + }, + FINISHED(3) { + @Override + public InviteeProtocolState next(InviteeAction a) { + if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT; + return FINISHED; + } + }, + LEFT(4) { + @Override + public InviteeProtocolState next(InviteeAction a) { + if (a == LOCAL_LEAVE) return ERROR; + return LEFT; + } + }; + + private final int value; + + InviteeProtocolState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static InviteeProtocolState fromValue(int value) { + for (InviteeProtocolState s : values()) { + if (s.value == value) return s; + } + throw new IllegalArgumentException(); + } + + public InviteeProtocolState next(InviteeAction a) { + return this; + } +} diff --git a/briar-api/src/org/briarproject/api/forum/SharerAction.java b/briar-api/src/org/briarproject/api/forum/SharerAction.java new file mode 100644 index 0000000000000000000000000000000000000000..39796f2c828c10f01bcd6ae3a05f7d258792e11e --- /dev/null +++ b/briar-api/src/org/briarproject/api/forum/SharerAction.java @@ -0,0 +1,34 @@ +package org.briarproject.api.forum; + +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; + +public enum SharerAction { + + LOCAL_INVITATION, + LOCAL_LEAVE, + LOCAL_ABORT, + REMOTE_ACCEPT, + REMOTE_DECLINE, + REMOTE_LEAVE, + REMOTE_ABORT; + + public static SharerAction getLocal(long type) { + if (type == SHARE_MSG_TYPE_INVITATION) return LOCAL_INVITATION; + if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE; + if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT; + return null; + } + + public static SharerAction getRemote(long type) { + if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT; + if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE; + if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE; + if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT; + return null; + } + +} diff --git a/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java b/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java new file mode 100644 index 0000000000000000000000000000000000000000..b948a9483d980d1cd9a0eeed91d81a6edcdf5511 --- /dev/null +++ b/briar-api/src/org/briarproject/api/forum/SharerProtocolState.java @@ -0,0 +1,62 @@ +package org.briarproject.api.forum; + +import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION; +import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE; +import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT; +import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE; +import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE; + +public enum SharerProtocolState { + + ERROR(0), + PREPARE_INVITATION(1) { + @Override + public SharerProtocolState next(SharerAction a) { + if (a == LOCAL_INVITATION) return AWAIT_RESPONSE; + return ERROR; + } + }, + AWAIT_RESPONSE(2) { + @Override + public SharerProtocolState next(SharerAction a) { + if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED; + if (a == LOCAL_LEAVE) return LEFT; + return ERROR; + } + }, + FINISHED(3) { + @Override + public SharerProtocolState next(SharerAction a) { + if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT; + return FINISHED; + } + }, + LEFT(4) { + @Override + public SharerProtocolState next(SharerAction a) { + if (a == LOCAL_LEAVE) return ERROR; + return LEFT; + } + }; + + private final int value; + + SharerProtocolState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static SharerProtocolState fromValue(int value) { + for (SharerProtocolState s : values()) { + if (s.value == value) return s; + } + throw new IllegalArgumentException(); + } + + public SharerProtocolState next(SharerAction a) { + return this; + } +} diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java index 8c9de76dfaf8eea628c8bd67946429acc1858918..d860726a7565fe5a613e574e55c19bf1524b908d 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionMessage.java @@ -1,61 +1,36 @@ package org.briarproject.api.introduction; import org.briarproject.api.clients.SessionId; +import org.briarproject.api.messaging.BaseMessage; import org.briarproject.api.sync.MessageId; import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -abstract public class IntroductionMessage { + public class IntroductionMessage extends BaseMessage { private final SessionId sessionId; private final MessageId messageId; private final int role; - private final long time; - private final boolean local, sent, seen, read; public IntroductionMessage(SessionId sessionId, MessageId messageId, int role, long time, boolean local, boolean sent, boolean seen, boolean read) { + super(messageId, time, local, read, sent, seen); this.sessionId = sessionId; this.messageId = messageId; this.role = role; - this.time = time; - this.local = local; - this.sent = sent; - this.seen = seen; - this.read = read; } public SessionId getSessionId() { return sessionId; } - public long getTime() { - return time; - } - public MessageId getMessageId() { return messageId; } - public boolean isLocal() { - return local; - } - - public boolean isSent() { - return sent; - } - - public boolean isSeen() { - return seen; - } - - public boolean isRead() { - return read; - } - public boolean isIntroducer() { return role == ROLE_INTRODUCER; } diff --git a/briar-api/src/org/briarproject/api/messaging/BaseMessage.java b/briar-api/src/org/briarproject/api/messaging/BaseMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..c83350a09e4c28feffb874bae9038077669c84b9 --- /dev/null +++ b/briar-api/src/org/briarproject/api/messaging/BaseMessage.java @@ -0,0 +1,45 @@ +package org.briarproject.api.messaging; + +import org.briarproject.api.sync.MessageId; + +public abstract class BaseMessage { + + private final MessageId id; + private final long timestamp; + private final boolean local, read, sent, seen; + + public BaseMessage(MessageId id, long timestamp, boolean local, + boolean read, boolean sent, boolean seen) { + + this.id = id; + this.timestamp = timestamp; + this.local = local; + this.read = read; + this.sent = sent; + this.seen = seen; + } + + public MessageId getId() { + return id; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean isLocal() { + return local; + } + + public boolean isRead() { + return read; + } + + public boolean isSent() { + return sent; + } + + public boolean isSeen() { + return seen; + } +} diff --git a/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java b/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java index 9db8854a1d819a04df07b8446fd30adaa8ea4ff4..f1c8eb51fe6d0246e99e16b0c7435c501e7bb7e4 100644 --- a/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java +++ b/briar-api/src/org/briarproject/api/messaging/PrivateMessageHeader.java @@ -2,50 +2,19 @@ package org.briarproject.api.messaging; import org.briarproject.api.sync.MessageId; -public class PrivateMessageHeader { +public class PrivateMessageHeader extends BaseMessage { - private final MessageId id; - private final long timestamp; private final String contentType; - private final boolean local, read, sent, seen; public PrivateMessageHeader(MessageId id, long timestamp, String contentType, boolean local, boolean read, boolean sent, boolean seen) { - this.id = id; - this.timestamp = timestamp; - this.contentType = contentType; - this.local = local; - this.read = read; - this.sent = sent; - this.seen = seen; - } - public MessageId getId() { - return id; + super(id, timestamp, local, read, sent, seen); + this.contentType = contentType; } public String getContentType() { return contentType; } - - public long getTimestamp() { - return timestamp; - } - - public boolean isLocal() { - return local; - } - - public boolean isRead() { - return read; - } - - public boolean isSent() { - return sent; - } - - public boolean isSeen() { - return seen; - } } diff --git a/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java b/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java index 5380652c6d84dccb25ef4cab8c9a2ea837d5144d..e8d0b5e2f7e8633fba85b00d79e21af9ddd0847d 100644 --- a/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java +++ b/briar-core/src/org/briarproject/clients/BdfIncomingMessageHook.java @@ -24,7 +24,7 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook, protected final MetadataParser metadataParser; protected BdfIncomingMessageHook(ClientHelper clientHelper, - MetadataParser metadataParser, Clock clock) { + MetadataParser metadataParser) { this.clientHelper = clientHelper; this.metadataParser = metadataParser; } diff --git a/briar-core/src/org/briarproject/forum/ForumListValidator.java b/briar-core/src/org/briarproject/forum/ForumListValidator.java deleted file mode 100644 index 9e6947a377f4d12b4d02bff1a6b380a1831798e6..0000000000000000000000000000000000000000 --- a/briar-core/src/org/briarproject/forum/ForumListValidator.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.briarproject.forum; - -import org.briarproject.api.FormatException; -import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.data.BdfDictionary; -import org.briarproject.api.data.BdfList; -import org.briarproject.api.data.MetadataEncoder; -import org.briarproject.api.sync.Group; -import org.briarproject.api.sync.Message; -import org.briarproject.api.system.Clock; -import org.briarproject.clients.BdfMessageValidator; - -import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; -import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; - -class ForumListValidator extends BdfMessageValidator { - - ForumListValidator(ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { - super(clientHelper, metadataEncoder, clock); - } - - @Override - protected BdfDictionary validateMessage(Message m, Group g, - BdfList body) throws FormatException { - // Version, forum list - checkSize(body, 2); - // Version - long version = body.getLong(0); - if (version < 0) throw new FormatException(); - // Forum list - BdfList forumList = body.getList(1); - for (int i = 0; i < forumList.size(); i++) { - BdfList forum = forumList.getList(i); - // Name, salt - checkSize(forum, 2); - String name = forum.getString(0); - checkLength(name, 1, MAX_FORUM_NAME_LENGTH); - byte[] salt = forum.getRaw(1); - checkLength(salt, FORUM_SALT_LENGTH); - } - // Return the metadata - BdfDictionary meta = new BdfDictionary(); - meta.put("version", version); - meta.put("local", false); - return meta; - } -} diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index 63d4a2db832a983d99f774c43edf5127d161603c..d2a2b14d8a98f20f97fee4c14680b7c82e1adb56 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -141,15 +141,21 @@ class ForumManagerImpl implements ForumManager { @Override public Forum getForum(GroupId g) throws DbException { + Forum forum; + Transaction txn = db.startTransaction(true); try { - Group group; - Transaction txn = db.startTransaction(true); - try { - group = db.getGroup(txn, g); - txn.setComplete(); - } finally { - db.endTransaction(txn); - } + forum = getForum(txn, g); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return forum; + } + + @Override + public Forum getForum(Transaction txn, GroupId g) throws DbException { + try { + Group group = db.getGroup(txn, g); return parseForum(group); } catch (FormatException e) { throw new DbException(e); diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java index ac0ff20616b31e0c7de10f3eb345fb7350e6046d..e6d7b55ac4197043c14d50185053926ea1463253 100644 --- a/briar-core/src/org/briarproject/forum/ForumModule.java +++ b/briar-core/src/org/briarproject/forum/ForumModule.java @@ -1,6 +1,7 @@ package org.briarproject.forum; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; @@ -26,11 +27,11 @@ import dagger.Provides; public class ForumModule { public static class EagerSingletons { - @Inject - ForumListValidator forumListValidator; @Inject ForumPostValidator forumPostValidator; @Inject + ForumSharingValidator forumSharingValidator; + @Inject ForumSharingManager forumSharingManager; } @@ -63,13 +64,15 @@ public class ForumModule { @Provides @Singleton - ForumListValidator provideForumListValidator( - ValidationManager validationManager, ClientHelper clientHelper, + ForumSharingValidator provideSharingValidator( + MessageQueueManager messageQueueManager, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { - ForumListValidator validator = new ForumListValidator(clientHelper, + + ForumSharingValidator validator = new ForumSharingValidator(clientHelper, metadataEncoder, clock); - validationManager.registerMessageValidator( + messageQueueManager.registerMessageValidator( ForumSharingManagerImpl.CLIENT_ID, validator); + return validator; } @@ -78,15 +81,17 @@ public class ForumModule { ForumSharingManager provideForumSharingManager( LifecycleManager lifecycleManager, ContactManager contactManager, - ValidationManager validationManager, + MessageQueueManager messageQueueManager, ForumManager forumManager, ForumSharingManagerImpl forumSharingManager) { + lifecycleManager.registerClient(forumSharingManager); contactManager.registerAddContactHook(forumSharingManager); contactManager.registerRemoveContactHook(forumSharingManager); - validationManager.registerIncomingMessageHook( + messageQueueManager.registerIncomingMessageHook( ForumSharingManagerImpl.CLIENT_ID, forumSharingManager); forumManager.registerRemoveForumHook(forumSharingManager); + return forumSharingManager; } } diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java index 62501e8e50ccd007c3d5bd9809fb28e882491999..c970571a9c02523cea3db2b5844f346de72502c6 100644 --- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java @@ -1,74 +1,140 @@ package org.briarproject.forum; +import org.briarproject.api.Bytes; import org.briarproject.api.FormatException; import org.briarproject.api.clients.Client; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.MessageQueueManager; import org.briarproject.api.clients.PrivateGroupFactory; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.Event; import org.briarproject.api.forum.Forum; +import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumSharingManager; +import org.briarproject.api.forum.InviteeAction; +import org.briarproject.api.forum.InviteeProtocolState; +import org.briarproject.api.forum.SharerAction; +import org.briarproject.api.forum.SharerProtocolState; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; -import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; +import org.briarproject.api.sync.MessageStatus; import org.briarproject.api.system.Clock; +import org.briarproject.clients.BdfIncomingMessageHook; import org.briarproject.util.StringUtils; import java.io.IOException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; 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.logging.Logger; import javax.inject.Inject; -import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.clients.ProtocolEngine.StateUpdate; +import static org.briarproject.api.forum.ForumConstants.CONTACT_ID; +import static org.briarproject.api.forum.ForumConstants.FORUM_ID; +import static org.briarproject.api.forum.ForumConstants.FORUM_NAME; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; +import static org.briarproject.api.forum.ForumConstants.GROUP_ID; +import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG; +import static org.briarproject.api.forum.ForumConstants.IS_SHARER; +import static org.briarproject.api.forum.ForumConstants.LOCAL; +import static org.briarproject.api.forum.ForumConstants.READ; +import static org.briarproject.api.forum.ForumConstants.SESSION_ID; +import static org.briarproject.api.forum.ForumConstants.SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; +import static org.briarproject.api.forum.ForumConstants.STATE; +import static org.briarproject.api.forum.ForumConstants.STORAGE_ID; +import static org.briarproject.api.forum.ForumConstants.TASK; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM; +import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM; +import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TIME; +import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TYPE; import static org.briarproject.api.forum.ForumManager.RemoveForumHook; +import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_INVITATION; +import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_LOCAL_RESPONSE; +import static org.briarproject.api.forum.SharerProtocolState.PREPARE_INVITATION; -class ForumSharingManagerImpl implements ForumSharingManager, Client, - AddContactHook, RemoveContactHook, IncomingMessageHook, - RemoveForumHook { +class ForumSharingManagerImpl extends BdfIncomingMessageHook + implements ForumSharingManager, Client, RemoveForumHook, + AddContactHook, RemoveContactHook { static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString( "cd11a5d04dccd9e2931d6fc3df456313" + "63bb3e9d9d0e9405fccdb051f41f5449")); + private static final Logger LOG = + Logger.getLogger(ForumSharingManagerImpl.class.getName()); + private final DatabaseComponent db; private final ForumManager forumManager; - private final ClientHelper clientHelper; + private final MessageQueueManager messageQueueManager; + private final MetadataEncoder metadataEncoder; + private final SecureRandom random; private final PrivateGroupFactory privateGroupFactory; private final Clock clock; + private final Group localGroup; @Inject ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager, - ClientHelper clientHelper, PrivateGroupFactory privateGroupFactory, + MessageQueueManager messageQueueManager, ClientHelper clientHelper, + MetadataParser metadataParser, MetadataEncoder metadataEncoder, + SecureRandom random, PrivateGroupFactory privateGroupFactory, Clock clock) { + super(clientHelper, metadataParser); this.db = db; this.forumManager = forumManager; - this.clientHelper = clientHelper; + this.messageQueueManager = messageQueueManager; + this.metadataEncoder = metadataEncoder; + this.random = random; this.privateGroupFactory = privateGroupFactory; this.clock = clock; + localGroup = privateGroupFactory.createLocalGroup(getClientId()); } @Override public void createLocalState(Transaction txn) throws DbException { + db.addGroup(txn, localGroup); // Ensure we've set things up for any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @@ -85,7 +151,10 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, db.setVisibleToContact(txn, c.getId(), g.getId(), true); // Attach the contact ID to the group BdfDictionary meta = new BdfDictionary(); - meta.put("contactId", c.getId().getInt()); + meta.put(CONTACT_ID, c.getId().getInt()); + meta.put(TO_BE_SHARED_BY_US, new BdfList()); + meta.put(SHARED_BY_US, new BdfList()); + meta.put(SHARED_WITH_US, new BdfList()); clientHelper.mergeGroupMetadata(txn, g.getId(), meta); } catch (FormatException e) { throw new DbException(e); @@ -94,30 +163,228 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, @Override public void removingContact(Transaction txn, Contact c) throws DbException { + // clean up session states with that contact from localGroup + Long id = (long) c.getId().getInt(); + try { + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) { + BdfDictionary d = entry.getValue(); + if (id.equals(d.getLong(CONTACT_ID))) { + deleteMessage(txn, entry.getKey()); + } + } + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + + // remove the contact group (all messages will be removed with it) db.removeGroup(txn, getContactGroup(c)); } @Override - public void incomingMessage(Transaction txn, Message m, Metadata meta) + protected void incomingMessage(Transaction txn, Message m, BdfList body, + BdfDictionary msg) throws DbException, FormatException { + + SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); + long type = msg.getLong(TYPE); + if (type == SHARE_MSG_TYPE_INVITATION) { + // we are an invitee who just received a new invitation + boolean stateExists = true; + try { + // check if we have a session with that ID already + getSessionState(txn, sessionId, false); + } catch (FormatException e) { + // this is what we would expect under normal circumstances + stateExists = false; + } + try { + // check if we already have a state with that sessionId + if (stateExists) throw new FormatException(); + + // check if forum can be shared + Forum f = forumManager.createForum(msg.getString(FORUM_NAME), + msg.getRaw(FORUM_SALT)); + ContactId contactId = getContactId(txn, m.getGroupId()); + Contact contact = db.getContact(txn, contactId); + if (!canBeShared(txn, f.getId(), contact)) + throw new FormatException(); + + // initialize state and process invitation + BdfDictionary state = + initializeInviteeState(txn, contactId, msg); + InviteeEngine engine = new InviteeEngine(); + processStateUpdate(txn, m.getId(), + engine.onMessageReceived(state, msg)); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + deleteMessage(txn, m.getId()); + } + } else if (type == SHARE_MSG_TYPE_ACCEPT || + type == SHARE_MSG_TYPE_DECLINE) { + // we are a sharer who just received a response + BdfDictionary state = getSessionState(txn, sessionId, true); + SharerEngine engine = new SharerEngine(); + processStateUpdate(txn, m.getId(), + engine.onMessageReceived(state, msg)); + } else if (type == SHARE_MSG_TYPE_LEAVE || + type == SHARE_MSG_TYPE_ABORT) { + // we don't know who we are, so figure it out + BdfDictionary state = getSessionState(txn, sessionId, true); + if (state.getBoolean(IS_SHARER)) { + // we are a sharer and the invitee wants to leave or abort + SharerEngine engine = new SharerEngine(); + processStateUpdate(txn, m.getId(), + engine.onMessageReceived(state, msg)); + } else { + // we are an invitee and the sharer wants to leave or abort + InviteeEngine engine = new InviteeEngine(); + processStateUpdate(txn, m.getId(), + engine.onMessageReceived(state, msg)); + } + } else { + // message has passed validator, so that should never happen + throw new RuntimeException("Illegal Forum Sharing Message"); + } + } + + @Override + public ClientId getClientId() { + return CLIENT_ID; + } + + @Override + public void sendForumInvitation(GroupId groupId, ContactId contactId, + String msg) throws DbException { + + Transaction txn = db.startTransaction(false); + try { + // initialize local state for sharer + Forum f = forumManager.getForum(txn, groupId); + BdfDictionary localState = initializeSharerState(txn, f, contactId); + + // define action + BdfDictionary localAction = new BdfDictionary(); + localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION); + if (!StringUtils.isNullOrEmpty(msg)) { + localAction.put(INVITATION_MSG, msg); + } + + // start engine and process its state update + SharerEngine engine = new SharerEngine(); + processStateUpdate(txn, null, + engine.onLocalAction(localState, localAction)); + + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(); + } finally { + db.endTransaction(txn); + } + } + + @Override + public void respondToInvitation(Forum f, boolean accept) throws DbException { + + Transaction txn = db.startTransaction(false); try { - ContactId contactId = getContactId(txn, m.getGroupId()); - setForumVisibility(txn, contactId, getVisibleForums(txn, m)); + // find session state based on forum + BdfDictionary localState = getSessionStateForResponse(txn, f); + + // define action + BdfDictionary localAction = new BdfDictionary(); + if (accept) { + localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT); + } else { + localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE); + } + + // start engine and process its state update + InviteeEngine engine = new InviteeEngine(); + processStateUpdate(txn, null, + engine.onLocalAction(localState, localAction)); + + txn.setComplete(); } catch (FormatException e) { throw new DbException(e); + } finally { + db.endTransaction(txn); } } @Override - public ClientId getClientId() { - return CLIENT_ID; + public Collection<ForumInvitationMessage> getForumInvitationMessages( + ContactId contactId) throws DbException { + + Transaction txn = db.startTransaction(false); + try { + Contact contact = db.getContact(txn, contactId); + Group group = getContactGroup(contact); + + Collection<ForumInvitationMessage> list = + new ArrayList<ForumInvitationMessage>(); + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, group.getId()); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + BdfDictionary msg = m.getValue(); + try { + if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION) + continue; + + MessageStatus status = + db.getMessageStatus(txn, contactId, m.getKey()); + SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); + String name = msg.getString(FORUM_NAME); + String message = msg.getOptionalString(INVITATION_MSG); + long time = msg.getLong(TIME); + boolean local = msg.getBoolean(LOCAL); + boolean read = msg.getBoolean(READ, false); + boolean available = false; + if (!local) { + // figure out whether the forum is still available + BdfDictionary sessionState = + getSessionState(txn, sessionId, true); + InviteeProtocolState state = InviteeProtocolState + .fromValue( + sessionState.getLong(STATE).intValue()); + available = state == AWAIT_LOCAL_RESPONSE; + } + ForumInvitationMessage im = + new ForumInvitationMessage(m.getKey(), sessionId, + contactId, name, message, available, time, + local, status.isSent(), status.isSeen(), + read); + list.add(im); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + txn.setComplete(); + return list; + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } } @Override public void removingForum(Transaction txn, Forum f) throws DbException { try { - for (Contact c : db.getContacts(txn)) - removeFromList(txn, getContactGroup(c).getId(), f); + for (Contact c : db.getContacts(txn)) { + GroupId g = getContactGroup(c).getId(); + if (removeFromList(txn, g, TO_BE_SHARED_BY_US, f)) { + leaveForum(txn, c.getId(), f); + } + if (removeFromList(txn, g, SHARED_BY_US, f)) { + leaveForum(txn, c.getId(), f); + } + if (removeFromList(txn, g, SHARED_WITH_US, f)) { + leaveForum(txn, c.getId(), f); + } + } } catch (IOException e) { throw new DbException(e); } @@ -135,16 +402,11 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, // Get all forums shared by contacts for (Contact c : db.getContacts(txn)) { Group g = getContactGroup(c); - // Find the latest update version - LatestUpdate latest = findLatest(txn, g.getId(), false); - if (latest != null) { - // Retrieve and parse the latest update - BdfList message = clientHelper.getMessageAsList(txn, - latest.messageId); - for (Forum f : parseForumList(message)) { - if (!subscribed.contains(f.getGroup())) - available.add(f); - } + List<Forum> forums = + getForumList(txn, g.getId(), SHARED_WITH_US); + for (Forum f : forums) { + if (!subscribed.contains(f.getGroup())) + available.add(f); } } txn.setComplete(); @@ -164,7 +426,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, Transaction txn = db.startTransaction(true); try { for (Contact c : db.getContacts(txn)) { - if (listContains(txn, getContactGroup(c).getId(), g, false)) + GroupId contactGroup = getContactGroup(c).getId(); + if (listContains(txn, contactGroup, g, SHARED_WITH_US)) subscribers.add(c); } txn.setComplete(); @@ -184,7 +447,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, Transaction txn = db.startTransaction(true); try { for (Contact c : db.getContacts(txn)) { - if (listContains(txn, getContactGroup(c).getId(), g, true)) + GroupId contactGroup = getContactGroup(c).getId(); + if (listContains(txn, contactGroup, g, SHARED_BY_US)) shared.add(c.getId()); } txn.setComplete(); @@ -198,178 +462,417 @@ class ForumSharingManagerImpl implements ForumSharingManager, Client, } @Override - public void setSharedWith(GroupId g, Collection<ContactId> shared) + public boolean canBeShared(GroupId g, Contact c) throws DbException { + boolean canBeShared; + Transaction txn = db.startTransaction(true); + try { + canBeShared = canBeShared(txn, g, c); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + return canBeShared; + } + + private boolean canBeShared(Transaction txn, GroupId g, Contact c) throws DbException { + + try { + GroupId contactGroup = getContactGroup(c).getId(); + return !listContains(txn, contactGroup, g, SHARED_BY_US) && + !listContains(txn, contactGroup, g, SHARED_WITH_US) && + !listContains(txn, contactGroup, g, TO_BE_SHARED_BY_US); + } catch (FormatException e) { + throw new DbException(e); + } + } + + private BdfDictionary initializeSharerState(Transaction txn, Forum f, + ContactId contactId) throws FormatException, DbException { + + Contact c = db.getContact(txn, contactId); + Group group = getContactGroup(c); + + // create local message to keep engine state + long now = clock.currentTimeMillis(); + Bytes salt = new Bytes(new byte[FORUM_SALT_LENGTH]); + random.nextBytes(salt.getBytes()); + Message m = clientHelper.createMessage(localGroup.getId(), now, + BdfList.of(salt)); + MessageId sessionId = m.getId(); + + BdfDictionary d = new BdfDictionary(); + d.put(SESSION_ID, sessionId); + d.put(STORAGE_ID, sessionId); + d.put(GROUP_ID, group.getId()); + d.put(IS_SHARER, true); + d.put(STATE, PREPARE_INVITATION.getValue()); + d.put(CONTACT_ID, contactId.getInt()); + d.put(FORUM_ID, f.getId()); + d.put(FORUM_NAME, f.getName()); + d.put(FORUM_SALT, f.getSalt()); + + // save local state to database + clientHelper.addLocalMessage(txn, m, getClientId(), d, false); + + return d; + } + + private BdfDictionary initializeInviteeState(Transaction txn, + ContactId contactId, BdfDictionary msg) + throws FormatException, DbException { + + Contact c = db.getContact(txn, contactId); + Group group = getContactGroup(c); + String name = msg.getString(FORUM_NAME); + byte[] salt = msg.getRaw(FORUM_SALT); + Forum f = forumManager.createForum(name, salt); + + // create local message to keep engine state + long now = clock.currentTimeMillis(); + Bytes mSalt = new Bytes(new byte[FORUM_SALT_LENGTH]); + random.nextBytes(mSalt.getBytes()); + Message m = clientHelper.createMessage(localGroup.getId(), now, + BdfList.of(mSalt)); + + BdfDictionary d = new BdfDictionary(); + d.put(SESSION_ID, msg.getRaw(SESSION_ID)); + d.put(STORAGE_ID, m.getId()); + d.put(GROUP_ID, group.getId()); + d.put(IS_SHARER, false); + d.put(STATE, AWAIT_INVITATION.getValue()); + d.put(CONTACT_ID, contactId.getInt()); + d.put(FORUM_ID, f.getId()); + d.put(FORUM_NAME, name); + d.put(FORUM_SALT, salt); + + // save local state to database + clientHelper.addLocalMessage(txn, m, getClientId(), d, false); + + return d; + } + + private BdfDictionary getSessionState(Transaction txn, SessionId sessionId, + boolean warn) throws DbException, FormatException { + try { - Transaction txn = db.startTransaction(false); + // we should be able to get the sharer state directly from sessionId + return clientHelper.getMessageMetadataAsDictionary(txn, sessionId); + } catch (NoSuchMessageException e) { + // State not found directly, so iterate over all states + // to find state for invitee + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + BdfDictionary state = m.getValue(); + if (Arrays.equals(state.getRaw(SESSION_ID), + sessionId.getBytes())) { + return state; + } + } + if (warn && LOG.isLoggable(WARNING)) { + LOG.warning( + "No session state found for message with session ID " + + Arrays.hashCode(sessionId.getBytes())); + } + throw new FormatException(); + } + } + + private BdfDictionary getSessionStateForResponse(Transaction txn, Forum f) + throws DbException, FormatException { + + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + BdfDictionary d = m.getValue(); try { - // Retrieve the forum - Forum f = parseForum(db.getGroup(txn, g)); - // Update the list shared with each contact - shared = new HashSet<ContactId>(shared); - for (Contact c : db.getContacts(txn)) { - Group cg = getContactGroup(c); - if (shared.contains(c.getId())) { - if (addToList(txn, cg.getId(), f)) { - if (listContains(txn, cg.getId(), g, false)) - db.setVisibleToContact(txn, c.getId(), g, true); - } - } else { - removeFromList(txn, cg.getId(), f); - db.setVisibleToContact(txn, c.getId(), g, false); + InviteeProtocolState state = InviteeProtocolState + .fromValue(d.getLong(STATE).intValue()); + if (state == AWAIT_LOCAL_RESPONSE) { + byte[] id = d.getRaw(FORUM_ID); + if (Arrays.equals(f.getId().getBytes(), id)) { + // Note that there should always be only one session + // in this state for the same forum + return d; } } - txn.setComplete(); - } finally { - db.endTransaction(txn); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - } catch (FormatException e) { - throw new DbException(e); } + throw new DbException(); } - private Group getContactGroup(Contact c) { - return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); + private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f, + ContactId c) throws DbException, FormatException { + + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId()); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + BdfDictionary d = m.getValue(); + try { + // check that this session is with the right contact + if (c.getInt() != d.getLong(CONTACT_ID)) continue; + // check that a forum get be left in current session + int intState = d.getLong(STATE).intValue(); + if (d.getBoolean(IS_SHARER)) { + SharerProtocolState state = + SharerProtocolState.fromValue(intState); + if (state.next(SharerAction.LOCAL_LEAVE) == + SharerProtocolState.ERROR) continue; + } else { + InviteeProtocolState state = InviteeProtocolState + .fromValue(intState); + if (state.next(InviteeAction.LOCAL_LEAVE) == + InviteeProtocolState.ERROR) continue; + } + // check that this state actually concerns this forum + String name = d.getString(FORUM_NAME); + byte[] salt = d.getRaw(FORUM_SALT); + if (name.equals(f.getName()) && + Arrays.equals(salt, f.getSalt())) { + // TODO what happens when there is more than one invitation? + return d; + } + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + throw new FormatException(); } - private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local) + private void processStateUpdate(Transaction txn, MessageId messageId, + StateUpdate<BdfDictionary, BdfDictionary> result) throws DbException, FormatException { - LatestUpdate latest = null; - Map<MessageId, BdfDictionary> metadata = - clientHelper.getMessageMetadataAsDictionary(txn, g); - for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) { - BdfDictionary meta = e.getValue(); - if (meta.getBoolean("local") != local) continue; - long version = meta.getLong("version"); - if (latest == null || version > latest.version) - latest = new LatestUpdate(e.getKey(), version); + + // perform actions based on new local state + performTasks(txn, result.localState); + + // save new local state + MessageId storageId = + new MessageId(result.localState.getRaw(STORAGE_ID)); + clientHelper.mergeMessageMetadata(txn, storageId, result.localState); + + // send messages + for (BdfDictionary d : result.toSend) { + sendMessage(txn, d); + } + + // broadcast events + for (Event event : result.toBroadcast) { + txn.attach(event); + } + + // delete message + if (result.deleteMessage && messageId != null) { + if (LOG.isLoggable(INFO)) { + LOG.info("Deleting message with id " + messageId.hashCode()); + } + db.deleteMessage(txn, messageId); + db.deleteMessageMetadata(txn, messageId); } - return latest; } - private List<Forum> parseForumList(BdfList message) throws FormatException { - // Version, forum list - BdfList forumList = message.getList(1); - List<Forum> forums = new ArrayList<Forum>(forumList.size()); - for (int i = 0; i < forumList.size(); i++) { - // Name, salt - BdfList forum = forumList.getList(i); - forums.add(forumManager - .createForum(forum.getString(0), forum.getRaw(1))); + private void performTasks(Transaction txn, BdfDictionary localState) + throws FormatException, DbException { + + if (!localState.containsKey(TASK)) return; + + // remember task and remove it from localState + long task = localState.getLong(TASK); + localState.put(TASK, BdfDictionary.NULL_VALUE); + + // get group ID for later + GroupId groupId = new GroupId(localState.getRaw(GROUP_ID)); + // get contact ID for later + ContactId contactId = + new ContactId(localState.getLong(CONTACT_ID).intValue()); + + // get forum for later + String name = localState.getString(FORUM_NAME); + byte[] salt = localState.getRaw(FORUM_SALT); + Forum f = forumManager.createForum(name, salt); + + // perform tasks + if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) { + addToList(txn, groupId, SHARED_WITH_US, f); + } + else if (task == TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US) { + removeFromList(txn, groupId, SHARED_WITH_US, f); + } + else if (task == TASK_ADD_SHARED_FORUM) { + db.addGroup(txn, f.getGroup()); + db.setVisibleToContact(txn, contactId, f.getId(), true); + } + else if (task == TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US) { + addToList(txn, groupId, TO_BE_SHARED_BY_US, f); + } + else if (task == TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US) { + removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f); + } + else if (task == TASK_SHARE_FORUM) { + db.setVisibleToContact(txn, contactId, f.getId(), true); + removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f); + addToList(txn, groupId, SHARED_BY_US, f); + } + else if (task == TASK_UNSHARE_FORUM_SHARED_BY_US) { + db.setVisibleToContact(txn, contactId, f.getId(), false); + removeFromList(txn, groupId, SHARED_BY_US, f); + } else if (task == TASK_UNSHARE_FORUM_SHARED_WITH_US) { + db.setVisibleToContact(txn, contactId, f.getId(), false); + removeFromList(txn, groupId, SHARED_WITH_US, f); } - return forums; } - private void storeMessage(Transaction txn, GroupId g, List<Forum> forums, - long version) throws DbException { - try { - BdfList body = encodeForumList(forums, version); - long now = clock.currentTimeMillis(); - Message m = clientHelper.createMessage(g, now, body); - BdfDictionary meta = new BdfDictionary(); - meta.put("version", version); - meta.put("local", true); - clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, true); - } catch (FormatException e) { - throw new RuntimeException(e); + private void sendMessage(Transaction txn, BdfDictionary m) + throws FormatException, DbException { + + BdfList list = encodeMessage(m); + byte[] body = clientHelper.toByteArray(list); + GroupId groupId = new GroupId(m.getRaw(GROUP_ID)); + Group group = db.getGroup(txn, groupId); + long timestamp = clock.currentTimeMillis(); + + // add message itself as metadata + m.put(LOCAL, true); + m.put(TIME, timestamp); + Metadata meta = metadataEncoder.encode(m); + + messageQueueManager + .sendMessage(txn, group, timestamp, body, meta); + } + + private BdfList encodeMessage(BdfDictionary m) throws FormatException { + long type = m.getLong(TYPE); + + BdfList list; + if (type == SHARE_MSG_TYPE_INVITATION) { + list = BdfList.of(type, + m.getRaw(SESSION_ID), + m.getString(FORUM_NAME), + m.getRaw(FORUM_SALT) + ); + String msg = m.getOptionalString(INVITATION_MSG); + if (msg != null) list.add(msg); + } else if (type == SHARE_MSG_TYPE_ACCEPT) { + list = BdfList.of(type, m.getRaw(SESSION_ID)); + } else if (type == SHARE_MSG_TYPE_DECLINE) { + list = BdfList.of(type, m.getRaw(SESSION_ID)); + } else if (type == SHARE_MSG_TYPE_LEAVE) { + list = BdfList.of(type, m.getRaw(SESSION_ID)); + } else if (type == SHARE_MSG_TYPE_ABORT) { + list = BdfList.of(type, m.getRaw(SESSION_ID)); + } else { + throw new FormatException(); } + return list; } - private BdfList encodeForumList(List<Forum> forums, long version) { - BdfList forumList = new BdfList(); - for (Forum f : forums) - forumList.add(BdfList.of(f.getName(), f.getSalt())); - return BdfList.of(version, forumList); + private Group getContactGroup(Contact c) { + return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); } private ContactId getContactId(Transaction txn, GroupId contactGroupId) throws DbException, FormatException { BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId); - return new ContactId(meta.getLong("contactId").intValue()); - } - - private Set<GroupId> getVisibleForums(Transaction txn, - Message remoteUpdate) throws DbException, FormatException { - // Get the latest local update - LatestUpdate local = findLatest(txn, remoteUpdate.getGroupId(), true); - // If there's no local update, no forums are visible - if (local == null) return Collections.emptySet(); - // Intersect the sets of shared forums - BdfList localMessage = clientHelper.getMessageAsList(txn, - local.messageId); - Set<Forum> shared = new HashSet<Forum>(parseForumList(localMessage)); - byte[] raw = remoteUpdate.getRaw(); - BdfList remoteMessage = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH, - raw.length - MESSAGE_HEADER_LENGTH); - shared.retainAll(parseForumList(remoteMessage)); - // Forums in the intersection should be visible - Set<GroupId> visible = new HashSet<GroupId>(shared.size()); - for (Forum f : shared) visible.add(f.getId()); - return visible; - } - - private void setForumVisibility(Transaction txn, ContactId c, - Set<GroupId> visible) throws DbException { - for (Group g : db.getGroups(txn, forumManager.getClientId())) { - boolean isVisible = db.isVisibleToContact(txn, c, g.getId()); - boolean shouldBeVisible = visible.contains(g.getId()); - if (isVisible && !shouldBeVisible) - db.setVisibleToContact(txn, c, g.getId(), false); - else if (!isVisible && shouldBeVisible) - db.setVisibleToContact(txn, c, g.getId(), true); - } + return new ContactId(meta.getLong(CONTACT_ID).intValue()); } - private Forum parseForum(Group g) throws FormatException { - byte[] descriptor = g.getDescriptor(); - // Name, salt - BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length); - return new Forum(g, forum.getString(0), forum.getRaw(1)); + private void leaveForum(Transaction txn, ContactId c, Forum f) + throws DbException, FormatException { + + BdfDictionary state = getSessionStateForLeaving(txn, f, c); + BdfDictionary action = new BdfDictionary(); + action.put(TYPE, SHARE_MSG_TYPE_LEAVE); + if (state.getBoolean(IS_SHARER)) { + SharerEngine engine = new SharerEngine(); + processStateUpdate(txn, null, + engine.onLocalAction(state, action)); + } else { + InviteeEngine engine = new InviteeEngine(); + processStateUpdate(txn, null, + engine.onLocalAction(state, action)); + } } - private boolean listContains(Transaction txn, GroupId g, GroupId forum, - boolean local) throws DbException, FormatException { - LatestUpdate latest = findLatest(txn, g, local); - if (latest == null) return false; - BdfList message = clientHelper.getMessageAsList(txn, latest.messageId); - List<Forum> list = parseForumList(message); - for (Forum f : list) if (f.getId().equals(forum)) return true; + private boolean listContains(Transaction txn, GroupId contactGroup, + GroupId forum, String key) throws DbException, FormatException { + + List<Forum> list = getForumList(txn, contactGroup, key); + for (Forum f : list) { + if (f.getId().equals(forum)) return true; + } return false; } - private boolean addToList(Transaction txn, GroupId g, Forum f) - throws DbException, FormatException { - LatestUpdate latest = findLatest(txn, g, true); - if (latest == null) { - storeMessage(txn, g, Collections.singletonList(f), 0); + private boolean addToList(Transaction txn, GroupId groupId, String key, + Forum f) throws DbException, FormatException { + + List<Forum> forums = getForumList(txn, groupId, key); + if (forums.contains(f)) return false; + forums.add(f); + storeForumList(txn, groupId, key, forums); + return true; + } + + private boolean removeFromList(Transaction txn, GroupId groupId, String key, + Forum f) throws DbException, FormatException { + + List<Forum> forums = getForumList(txn, groupId, key); + if (forums.remove(f)) { + storeForumList(txn, groupId, key, forums); return true; } - BdfList message = clientHelper.getMessageAsList(txn, latest.messageId); - List<Forum> list = parseForumList(message); - if (list.contains(f)) return false; - list.add(f); - storeMessage(txn, g, list, latest.version + 1); - return true; + return false; } - private void removeFromList(Transaction txn, GroupId g, Forum f) - throws DbException, FormatException { - LatestUpdate latest = findLatest(txn, g, true); - if (latest == null) return; - BdfList message = clientHelper.getMessageAsList(txn, latest.messageId); - List<Forum> list = parseForumList(message); - if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1); + private List<Forum> getForumList(Transaction txn, GroupId groupId, + String key) throws DbException, FormatException { + + BdfDictionary metadata = + clientHelper.getGroupMetadataAsDictionary(txn, groupId); + BdfList list = metadata.getList(key); + + return parseForumList(list); } - private static class LatestUpdate { + private void storeForumList(Transaction txn, GroupId groupId, String key, + List<Forum> forums) throws DbException, FormatException { + + BdfList list = encodeForumList(forums); + BdfDictionary metadata = BdfDictionary.of( + new BdfEntry(key, list) + ); + clientHelper.mergeGroupMetadata(txn, groupId, metadata); + } - private final MessageId messageId; - private final long version; + private BdfList encodeForumList(List<Forum> forums) { + BdfList forumList = new BdfList(); + for (Forum f : forums) + forumList.add(BdfList.of(f.getName(), f.getSalt())); + return forumList; + } - private LatestUpdate(MessageId messageId, long version) { - this.messageId = messageId; - this.version = version; + private List<Forum> parseForumList(BdfList list) throws FormatException { + List<Forum> forums = new ArrayList<Forum>(list.size()); + for (int i = 0; i < list.size(); i++) { + BdfList forum = list.getList(i); + forums.add(forumManager + .createForum(forum.getString(0), forum.getRaw(1))); } + return forums; } + + private void deleteMessage(Transaction txn, MessageId messageId) + throws DbException { + + if (LOG.isLoggable(INFO)) + LOG.info("Deleting message with ID: " + messageId.hashCode()); + + db.deleteMessage(txn, messageId); + db.deleteMessageMetadata(txn, messageId); + } + } diff --git a/briar-core/src/org/briarproject/forum/ForumSharingValidator.java b/briar-core/src/org/briarproject/forum/ForumSharingValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..c204623a27dee2dc193befcaaad12ec915a12cba --- /dev/null +++ b/briar-core/src/org/briarproject/forum/ForumSharingValidator.java @@ -0,0 +1,81 @@ +package org.briarproject.forum; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.Message; +import org.briarproject.api.system.Clock; +import org.briarproject.clients.BdfMessageValidator; + +import static org.briarproject.api.forum.ForumConstants.FORUM_NAME; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; +import static org.briarproject.api.forum.ForumConstants.GROUP_ID; +import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG; +import static org.briarproject.api.forum.ForumConstants.LOCAL; +import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; +import static org.briarproject.api.forum.ForumConstants.SESSION_ID; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; +import static org.briarproject.api.forum.ForumConstants.TIME; +import static org.briarproject.api.forum.ForumConstants.TYPE; +import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; + +class ForumSharingValidator extends BdfMessageValidator { + + ForumSharingValidator(ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + super(clientHelper, metadataEncoder, clock); + } + + @Override + protected BdfDictionary validateMessage(Message m, Group g, + BdfList body) throws FormatException { + + BdfDictionary d = new BdfDictionary(); + long type = body.getLong(0); + byte[] id = body.getRaw(1); + checkLength(id, SessionId.LENGTH); + + if (type == SHARE_MSG_TYPE_INVITATION) { + checkSize(body, 4, 5); + + String name = body.getString(2); + checkLength(name, 1, MAX_FORUM_NAME_LENGTH); + + byte[] salt = body.getRaw(3); + checkLength(salt, FORUM_SALT_LENGTH); + + d.put(FORUM_NAME, name); + d.put(FORUM_SALT, salt); + + if (body.size() > 4) { + String msg = body.getString(4); + checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH); + d.put(INVITATION_MSG, msg); + } + } else { + checkSize(body, 2); + if (type != SHARE_MSG_TYPE_ACCEPT && + type != SHARE_MSG_TYPE_DECLINE && + type != SHARE_MSG_TYPE_LEAVE && + type != SHARE_MSG_TYPE_ABORT) { + throw new FormatException(); + } + } + // Return the metadata + d.put(TYPE, type); + d.put(SESSION_ID, id); + d.put(GROUP_ID, m.getGroupId()); + d.put(LOCAL, false); + d.put(TIME, m.getTimestamp()); + return d; + } +} diff --git a/briar-core/src/org/briarproject/forum/InviteeEngine.java b/briar-core/src/org/briarproject/forum/InviteeEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..f208204db66386c080a5511a2ed75445879583e3 --- /dev/null +++ b/briar-core/src/org/briarproject/forum/InviteeEngine.java @@ -0,0 +1,265 @@ +package org.briarproject.forum; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ProtocolEngine; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.ForumInvitationReceivedEvent; +import org.briarproject.api.forum.Forum; +import org.briarproject.api.forum.InviteeAction; +import org.briarproject.api.forum.InviteeProtocolState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.forum.ForumConstants.CONTACT_ID; +import static org.briarproject.api.forum.ForumConstants.FORUM_NAME; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT; +import static org.briarproject.api.forum.ForumConstants.GROUP_ID; +import static org.briarproject.api.forum.ForumConstants.SESSION_ID; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; +import static org.briarproject.api.forum.ForumConstants.STATE; +import static org.briarproject.api.forum.ForumConstants.TASK; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM; +import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US; +import static org.briarproject.api.forum.ForumConstants.TYPE; +import static org.briarproject.api.forum.InviteeAction.LOCAL_ABORT; +import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT; +import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE; +import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE; +import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION; +import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE; +import static org.briarproject.api.forum.InviteeProtocolState.ERROR; +import static org.briarproject.api.forum.InviteeProtocolState.FINISHED; +import static org.briarproject.api.forum.InviteeProtocolState.LEFT; + +public class InviteeEngine + implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { + + private static final Logger LOG = + Logger.getLogger(SharerEngine.class.getName()); + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( + BdfDictionary localState, BdfDictionary localAction) { + + try { + InviteeProtocolState currentState = + getState(localState.getLong(STATE)); + long type = localAction.getLong(TYPE); + InviteeAction action = InviteeAction.getLocal(type); + InviteeProtocolState nextState = currentState.next(action); + localState.put(STATE, nextState.getValue()); + + if (action == LOCAL_ABORT && currentState != ERROR) { + return abortSession(currentState, localState); + } + + if (nextState == ERROR) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Error: Invalid action in state " + + currentState.name()); + } + return noUpdate(localState, true); + } + List<BdfDictionary> messages; + List<Event> events = Collections.emptyList(); + + if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { + BdfDictionary msg = BdfDictionary.of( + new BdfEntry(SESSION_ID, localState.getRaw(SESSION_ID)), + new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID)) + ); + if (action == LOCAL_ACCEPT) { + localState.put(TASK, TASK_ADD_SHARED_FORUM); + msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT); + } else { + localState.put(TASK, + TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US); + msg.put(TYPE, SHARE_MSG_TYPE_DECLINE); + } + messages = Collections.singletonList(msg); + logLocalAction(currentState, localState, msg); + } + else if (action == LOCAL_LEAVE) { + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, SHARE_MSG_TYPE_LEAVE); + msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); + messages = Collections.singletonList(msg); + logLocalAction(currentState, localState, msg); + } + else { + throw new IllegalArgumentException("Unknown Local Action"); + } + return new StateUpdate<BdfDictionary, BdfDictionary>(false, + false, localState, messages, events); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( + BdfDictionary localState, BdfDictionary msg) { + + try { + InviteeProtocolState currentState = + getState(localState.getLong(STATE)); + long type = msg.getLong(TYPE); + InviteeAction action = InviteeAction.getRemote(type); + InviteeProtocolState nextState = currentState.next(action); + localState.put(STATE, nextState.getValue()); + + logMessageReceived(currentState, nextState, type, msg); + + if (nextState == ERROR) { + if (currentState != ERROR) { + return abortSession(currentState, localState); + } else { + return noUpdate(localState, true); + } + } + + List<BdfDictionary> messages = Collections.emptyList(); + List<Event> events = Collections.emptyList(); + boolean deleteMsg = false; + + if (currentState == LEFT) { + // ignore and delete messages coming in while in that state + deleteMsg = true; + } + // the sharer left the forum she had shared with us + else if (action == REMOTE_LEAVE && currentState == FINISHED) { + localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US); + } + else if (currentState == FINISHED) { + // ignore and delete messages coming in while in that state + // note that LEAVE is possible, but was handled above + deleteMsg = true; + } + // the sharer left the forum before we couldn't even respond + else if (action == REMOTE_LEAVE) { + localState.put(TASK, TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US); + } + // we have just received our invitation + else if (action == REMOTE_INVITATION) { + localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US); + // TODO how to get the proper group here? + Forum forum = new Forum(null, localState.getString(FORUM_NAME), + localState.getRaw(FORUM_SALT)); + ContactId contactId = new ContactId( + localState.getLong(CONTACT_ID).intValue()); + Event event = new ForumInvitationReceivedEvent(forum, contactId); + events = Collections.singletonList(event); + } + else { + throw new IllegalArgumentException("Bad state"); + } + return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg, + false, localState, messages, events); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + private void logLocalAction(InviteeProtocolState state, + BdfDictionary localState, BdfDictionary msg) { + + if (!LOG.isLoggable(INFO)) return; + + String a = "response"; + if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave"; + + try { + LOG.info("Sending " + a + " in state " + state.name() + + " with session ID " + + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + + "Moving on to state " + + getState(localState.getLong(STATE)).name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private void logMessageReceived(InviteeProtocolState currentState, + InviteeProtocolState nextState, long type, BdfDictionary msg) { + if (!LOG.isLoggable(INFO)) return; + + try { + String t = "unknown"; + if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE"; + else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE"; + else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT"; + + LOG.info("Received " + t + " in state " + currentState.name() + + " with session ID " + + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + + "Moving on to state " + nextState.name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( + BdfDictionary localState, BdfDictionary delivered) { + try { + return noUpdate(localState, false); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; + } + } + + private InviteeProtocolState getState(Long state) { + return InviteeProtocolState.fromValue(state.intValue()); + } + + private StateUpdate<BdfDictionary, BdfDictionary> abortSession( + InviteeProtocolState currentState, BdfDictionary localState) + throws FormatException { + + if (LOG.isLoggable(WARNING)) { + LOG.warning("Aborting protocol session " + + Arrays.hashCode(localState.getRaw(SESSION_ID)) + + " in state " + currentState.name()); + } + + localState.put(STATE, ERROR.getValue()); + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, SHARE_MSG_TYPE_ABORT); + msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); + List<BdfDictionary> messages = Collections.singletonList(msg); + + List<Event> events = Collections.emptyList(); + + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } + + private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( + BdfDictionary localState, boolean delete) throws FormatException { + + return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false, + localState, Collections.<BdfDictionary>emptyList(), + Collections.<Event>emptyList()); + } +} diff --git a/briar-core/src/org/briarproject/forum/SharerEngine.java b/briar-core/src/org/briarproject/forum/SharerEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..05db965b80fb8f78aaa7b86c3f67cae3a838759f --- /dev/null +++ b/briar-core/src/org/briarproject/forum/SharerEngine.java @@ -0,0 +1,264 @@ +package org.briarproject.forum; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ProtocolEngine; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.ForumInvitationResponseReceivedEvent; +import org.briarproject.api.forum.SharerAction; +import org.briarproject.api.forum.SharerProtocolState; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.forum.ForumConstants.CONTACT_ID; +import static org.briarproject.api.forum.ForumConstants.FORUM_NAME; +import static org.briarproject.api.forum.ForumConstants.FORUM_SALT; +import static org.briarproject.api.forum.ForumConstants.GROUP_ID; +import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG; +import static org.briarproject.api.forum.ForumConstants.SESSION_ID; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; +import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE; +import static org.briarproject.api.forum.ForumConstants.STATE; +import static org.briarproject.api.forum.ForumConstants.TASK; +import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM; +import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US; +import static org.briarproject.api.forum.ForumConstants.TYPE; +import static org.briarproject.api.forum.SharerAction.LOCAL_ABORT; +import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION; +import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE; +import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT; +import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE; +import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE; +import static org.briarproject.api.forum.SharerProtocolState.ERROR; +import static org.briarproject.api.forum.SharerProtocolState.FINISHED; +import static org.briarproject.api.forum.SharerProtocolState.LEFT; + +public class SharerEngine + implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> { + + private static final Logger LOG = + Logger.getLogger(SharerEngine.class.getName()); + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction( + BdfDictionary localState, BdfDictionary localAction) { + + try { + SharerProtocolState currentState = + getState(localState.getLong(STATE)); + long type = localAction.getLong(TYPE); + SharerAction action = SharerAction.getLocal(type); + SharerProtocolState nextState = currentState.next(action); + localState.put(STATE, nextState.getValue()); + + if (action == LOCAL_ABORT && currentState != ERROR) { + return abortSession(currentState, localState); + } + + if (nextState == ERROR) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("Error: Invalid action in state " + + currentState.name()); + } + return noUpdate(localState, true); + } + List<BdfDictionary> messages; + List<Event> events = Collections.emptyList(); + + if (action == LOCAL_INVITATION) { + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, SHARE_MSG_TYPE_INVITATION); + msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); + msg.put(FORUM_NAME, localState.getString(FORUM_NAME)); + msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT)); + if (localAction.containsKey(INVITATION_MSG)) { + msg.put(INVITATION_MSG, + localAction.getString(INVITATION_MSG)); + } + messages = Collections.singletonList(msg); + logLocalAction(currentState, localState, msg); + + // remember that we offered to share this forum + localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US); + } + else if (action == LOCAL_LEAVE) { + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, SHARE_MSG_TYPE_LEAVE); + msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); + messages = Collections.singletonList(msg); + logLocalAction(currentState, localState, msg); + } + else { + throw new IllegalArgumentException("Unknown Local Action"); + } + return new StateUpdate<BdfDictionary, BdfDictionary>(false, + false, localState, messages, events); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived( + BdfDictionary localState, BdfDictionary msg) { + + try { + SharerProtocolState currentState = + getState(localState.getLong(STATE)); + long type = msg.getLong(TYPE); + SharerAction action = SharerAction.getRemote(type); + SharerProtocolState nextState = currentState.next(action); + localState.put(STATE, nextState.getValue()); + + logMessageReceived(currentState, nextState, type, msg); + + if (nextState == ERROR) { + if (currentState != ERROR) { + return abortSession(currentState, localState); + } else { + return noUpdate(localState, true); + } + } + List<BdfDictionary> messages = Collections.emptyList(); + List<Event> events = Collections.emptyList(); + boolean deleteMsg = false; + + if (currentState == LEFT) { + // ignore and delete messages coming in while in that state + deleteMsg = true; + } + else if (action == REMOTE_LEAVE) { + localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US); + } + else if (currentState == FINISHED) { + // ignore and delete messages coming in while in that state + // note that LEAVE is possible, but was handled above + deleteMsg = true; + } + // we have sent our invitation and just got a response + else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) { + if (action == REMOTE_ACCEPT) { + localState.put(TASK, TASK_SHARE_FORUM); + } else { + // this ensures that the forum can be shared again + localState.put(TASK, + TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US); + } + String name = localState.getString(FORUM_NAME); + ContactId c = new ContactId( + localState.getLong(CONTACT_ID).intValue()); + Event event = new ForumInvitationResponseReceivedEvent(name, c); + events = Collections.singletonList(event); + } + else { + throw new IllegalArgumentException("Bad state"); + } + return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg, + false, localState, messages, events); + } catch (FormatException e) { + throw new IllegalArgumentException(e); + } + } + + private void logLocalAction(SharerProtocolState state, + BdfDictionary localState, BdfDictionary msg) { + + if (!LOG.isLoggable(INFO)) return; + + String a = "invitation"; + if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave"; + + try { + LOG.info("Sending " + a + " in state " + state.name() + + " with session ID " + + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + + "Moving on to state " + + getState(localState.getLong(STATE)).name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + private void logMessageReceived(SharerProtocolState currentState, + SharerProtocolState nextState, long type, BdfDictionary msg) { + if (!LOG.isLoggable(INFO)) return; + + try { + String t = "unknown"; + if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT"; + else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE"; + else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE"; + else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT"; + + LOG.info("Received " + t + " in state " + currentState.name() + + " with session ID " + + Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " + + Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " + + "Moving on to state " + nextState.name() + ); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + + @Override + public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered( + BdfDictionary localState, BdfDictionary delivered) { + try { + return noUpdate(localState, false); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; + } + } + + private SharerProtocolState getState(Long state) { + return SharerProtocolState.fromValue(state.intValue()); + } + + private StateUpdate<BdfDictionary, BdfDictionary> abortSession( + SharerProtocolState currentState, BdfDictionary localState) + throws FormatException { + + if (LOG.isLoggable(WARNING)) { + LOG.warning("Aborting protocol session " + + Arrays.hashCode(localState.getRaw(SESSION_ID)) + + " in state " + currentState.name()); + } + + localState.put(STATE, ERROR.getValue()); + BdfDictionary msg = new BdfDictionary(); + msg.put(TYPE, SHARE_MSG_TYPE_ABORT); + msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); + msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); + List<BdfDictionary> messages = Collections.singletonList(msg); + + List<Event> events = Collections.emptyList(); + + return new StateUpdate<BdfDictionary, BdfDictionary>(false, false, + localState, messages, events); + } + + private StateUpdate<BdfDictionary, BdfDictionary> noUpdate( + BdfDictionary localState, boolean delete) throws FormatException { + + return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false, + localState, Collections.<BdfDictionary>emptyList(), + Collections.<Event>emptyList()); + } +} diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index 29954399aa576b254806dbc908ffe1210eead9d0..10cb986029898234208124b94e440b08e26267c0 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -3,6 +3,7 @@ package org.briarproject.introduction; import org.briarproject.api.FormatException; import org.briarproject.api.clients.Client; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager.AddContactHook; @@ -20,14 +21,12 @@ import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionResponse; -import org.briarproject.api.clients.SessionId; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; -import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfIncomingMessageHook; import org.briarproject.util.StringUtils; @@ -93,12 +92,11 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook @Inject IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper, - MetadataParser metadataParser, Clock clock, - IntroducerManager introducerManager, + MetadataParser metadataParser, IntroducerManager introducerManager, IntroduceeManager introduceeManager, IntroductionGroupFactory introductionGroupFactory) { - super(clientHelper, metadataParser, clock); + super(clientHelper, metadataParser); this.db = db; this.introducerManager = introducerManager; this.introduceeManager = introduceeManager; diff --git a/briar-tests/src/org/briarproject/forum/ForumListValidatorTest.java b/briar-tests/src/org/briarproject/forum/ForumSharingValidatorTest.java similarity index 77% rename from briar-tests/src/org/briarproject/forum/ForumListValidatorTest.java rename to briar-tests/src/org/briarproject/forum/ForumSharingValidatorTest.java index e10623d584a3e65ed4e662d17b43b4ac672569d2..0b87d04c19c36a768b5fcf4222b1445d5cffdaf1 100644 --- a/briar-tests/src/org/briarproject/forum/ForumListValidatorTest.java +++ b/briar-tests/src/org/briarproject/forum/ForumSharingValidatorTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import static org.junit.Assert.fail; -public class ForumListValidatorTest extends BriarTestCase { +public class ForumSharingValidatorTest extends BriarTestCase { @Test public void testUnitTestsExist() { diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java index fac0b0b32dbf483210e28468518613084069c53d..2b3bde49fb13e68d99e6a4ba6a0b8429cc5adf2c 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroductionManagerImplTest.java @@ -118,7 +118,7 @@ public class IntroductionManagerImplTest extends BriarTestCase { clock = context.mock(Clock.class); introductionManager = new IntroductionManagerImpl( - db, clientHelper, metadataParser, clock, introducerManager, + db, clientHelper, metadataParser, introducerManager, introduceeManager, introductionGroupFactory ); }