diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java index 7be98da26b1c6e09245272af845eeaee37bf00a3..14853dfb186df358fa232b8958aca2ebe3c25639 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java @@ -1,89 +1,47 @@ package org.briarproject.briar.sharing; import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.BdfMessageContext; import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.Group; -import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.system.Clock; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.client.BdfQueueMessageValidator; +import org.briarproject.briar.api.forum.Forum; +import org.briarproject.briar.api.forum.ForumFactory; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; -import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME; -import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT; import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; -import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG; -import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL; -import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; -import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE; -import static org.briarproject.briar.api.sharing.SharingConstants.TIME; -import static org.briarproject.briar.api.sharing.SharingConstants.TYPE; @Immutable @NotNullByDefault -class ForumSharingValidator extends BdfQueueMessageValidator { +class ForumSharingValidator extends SharingValidator { + + private final ForumFactory forumFactory; @Inject - ForumSharingValidator(ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { - super(clientHelper, metadataEncoder, clock); + ForumSharingValidator(MessageEncoder messageEncoder, + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock, ForumFactory forumFactory) { + super(messageEncoder, clientHelper, metadataEncoder, clock); + this.forumFactory = forumFactory; } @Override - protected BdfMessageContext 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_INVITATION_MESSAGE_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(LOCAL, false); - d.put(TIME, m.getTimestamp()); - return new BdfMessageContext(d); + protected GroupId validateDescriptor(BdfList descriptor) + throws FormatException { + checkSize(descriptor, 2); + String name = descriptor.getString(0); + checkLength(name, 1, MAX_FORUM_NAME_LENGTH); + byte[] salt = descriptor.getRaw(1); + checkLength(salt, FORUM_SALT_LENGTH); + Forum forum = forumFactory.createForum(name, salt); + return forum.getId(); } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..d19d55bdfa28491247e7fce29db3624d1dd859e9 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoder.java @@ -0,0 +1,39 @@ +package org.briarproject.briar.sharing; + +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; + +import javax.annotation.Nullable; + +@NotNullByDefault +interface MessageEncoder { + + BdfDictionary encodeMetadata(MessageType type, GroupId groupId, + long timestamp, boolean local, boolean read, boolean visible, + boolean available); + + void setVisibleInUi(BdfDictionary meta, boolean visible); + + void setAvailableToAnswer(BdfDictionary meta, boolean available); + + Message encodeInviteMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, BdfList descriptor, + @Nullable String message); + + Message encodeAcceptMessage(GroupId contactGroupId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId); + + Message encodeDeclineMessage(GroupId contactGroupId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId); + + Message encodeLeaveMessage(GroupId contactGroupId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId); + + Message encodeAbortMessage(GroupId contactGroupId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId); + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..90fa15b9f758f7271b95f04742ba1ac042aace24 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageEncoderImpl.java @@ -0,0 +1,135 @@ +package org.briarproject.briar.sharing; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageFactory; +import org.briarproject.bramble.api.sync.MessageId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.sharing.MessageType.ABORT; +import static org.briarproject.briar.sharing.MessageType.ACCEPT; +import static org.briarproject.briar.sharing.MessageType.DECLINE; +import static org.briarproject.briar.sharing.MessageType.INVITE; +import static org.briarproject.briar.sharing.MessageType.LEAVE; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_PRIVATE_GROUP_ID; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_READ; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_VISIBLE_IN_UI; + +@Immutable +@NotNullByDefault +class MessageEncoderImpl implements MessageEncoder { + + private final ClientHelper clientHelper; + private final MessageFactory messageFactory; + + @Inject + MessageEncoderImpl(ClientHelper clientHelper, + MessageFactory messageFactory) { + this.clientHelper = clientHelper; + this.messageFactory = messageFactory; + } + + @Override + public BdfDictionary encodeMetadata(MessageType type, + GroupId groupId, long timestamp, boolean local, boolean read, + boolean visible, boolean available) { + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue()); + meta.put(MSG_KEY_PRIVATE_GROUP_ID, groupId); + meta.put(MSG_KEY_TIMESTAMP, timestamp); + meta.put(MSG_KEY_LOCAL, local); + meta.put(MSG_KEY_READ, read); + meta.put(MSG_KEY_VISIBLE_IN_UI, visible); + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + return meta; + } + + @Override + public void setVisibleInUi(BdfDictionary meta, boolean visible) { + meta.put(MSG_KEY_VISIBLE_IN_UI, visible); + } + + @Override + public void setAvailableToAnswer(BdfDictionary meta, boolean available) { + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + } + + @Override + public Message encodeInviteMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, BdfList descriptor, + @Nullable String message) { + BdfList body = BdfList.of( + INVITE.getValue(), + previousMessageId, + descriptor, + message + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + @Override + public Message encodeAcceptMessage(GroupId contactGroupId, + GroupId privateGroupId, long timestamp, + @Nullable MessageId previousMessageId) { + return encodeMessage(ACCEPT, contactGroupId, privateGroupId, timestamp, + previousMessageId); + } + + @Override + public Message encodeDeclineMessage(GroupId contactGroupId, + GroupId privateGroupId, long timestamp, + @Nullable MessageId previousMessageId) { + return encodeMessage(DECLINE, contactGroupId, privateGroupId, timestamp, + previousMessageId); + } + + @Override + public Message encodeLeaveMessage(GroupId contactGroupId, + GroupId privateGroupId, long timestamp, + @Nullable MessageId previousMessageId) { + return encodeMessage(LEAVE, contactGroupId, privateGroupId, timestamp, + previousMessageId); + } + + @Override + public Message encodeAbortMessage(GroupId contactGroupId, + GroupId privateGroupId, long timestamp, + @Nullable MessageId previousMessageId) { + return encodeMessage(ABORT, contactGroupId, privateGroupId, timestamp, + previousMessageId); + } + + private Message encodeMessage(MessageType type, GroupId contactGroupId, + GroupId groupId, long timestamp, + @Nullable MessageId previousMessageId) { + BdfList body = BdfList.of( + type.getValue(), + groupId, + previousMessageId + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/MessageType.java b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageType.java new file mode 100644 index 0000000000000000000000000000000000000000..1af500a745d73bd0178c44551bc8f88a7fef6e65 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/MessageType.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.sharing; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum MessageType { + + INVITE(0), ACCEPT(1), DECLINE(2), LEAVE(3), ABORT(4); + + private final int value; + + MessageType(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + static MessageType fromValue(int value) throws FormatException { + for (MessageType m : values()) if (m.value == value) return m; + throw new FormatException(); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..ae3edc982f8b65cabca55253ee17dd925fa3cac5 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingConstants.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.sharing; + +import org.briarproject.briar.client.MessageTrackerConstants; + +interface SharingConstants { + + // Group metadata keys + String GROUP_KEY_CONTACT_ID = "contactId"; + + // Message metadata keys + String MSG_KEY_MESSAGE_TYPE = "messageType"; + String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId"; + String MSG_KEY_TIMESTAMP = "timestamp"; + String MSG_KEY_READ = MessageTrackerConstants.MSG_KEY_READ; + String MSG_KEY_LOCAL = "local"; + String MSG_KEY_VISIBLE_IN_UI = "visibleInUi"; + String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; + + // Session keys + String SESSION_KEY_SESSION_ID = "sessionId"; + String SESSION_KEY_GROUP_ID = "groupId"; + String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId"; + String SESSION_KEY_LAST_REMOTE_MESSAGE_ID = "lastRemoteMessageId"; + String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp"; + String SESSION_KEY_INVITE_TIMESTAMP = "inviteTimestamp"; + String SESSION_KEY_ROLE = "role"; + String SESSION_KEY_STATE = "state"; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java index f56e6b8ca88b99279002853e3508a9390ac8673e..9a6e4de732b55be92913eaf304c8e78061bb9237 100644 --- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java @@ -4,10 +4,12 @@ import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.client.MessageQueueManager; +import org.briarproject.briar.api.forum.ForumFactory; import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.messaging.ConversationManager; @@ -68,14 +70,15 @@ public class SharingModule { @Provides @Singleton ForumSharingValidator provideForumSharingValidator( - MessageQueueManager messageQueueManager, ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { - + ValidationManager validationManager, MessageEncoder messageEncoder, + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock, ForumFactory forumFactory) { ForumSharingValidator validator = - new ForumSharingValidator(clientHelper, metadataEncoder, clock); - messageQueueManager.registerMessageValidator( - ForumSharingManager.CLIENT_ID, validator); - + new ForumSharingValidator(messageEncoder, clientHelper, + metadataEncoder, clock, forumFactory); + validationManager + .registerMessageValidator(ForumSharingManager.CLIENT_ID, + validator); return validator; } @@ -98,4 +101,9 @@ public class SharingModule { return forumSharingManager; } + @Provides + MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) { + return messageEncoder; + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..631a6b30c7817e9edb279bd510e085bcd312bb91 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingValidator.java @@ -0,0 +1,101 @@ +package org.briarproject.briar.sharing; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.UniqueId; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.BdfMessageValidator; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; + +import java.util.Collections; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; +import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; +import static org.briarproject.briar.sharing.MessageType.INVITE; + +@Immutable +@NotNullByDefault +abstract class SharingValidator extends BdfMessageValidator { + + private final MessageEncoder messageEncoder; + + SharingValidator(MessageEncoder messageEncoder, ClientHelper clientHelper, + MetadataEncoder metadataEncoder, Clock clock) { + super(clientHelper, metadataEncoder, clock); + this.messageEncoder = messageEncoder; + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws FormatException { + MessageType type = MessageType.fromValue(body.getLong(0).intValue()); + switch (type) { + case INVITE: + return validateInviteMessage(m, body); + case ACCEPT: + case DECLINE: + case LEAVE: + case ABORT: + return validateNonInviteMessage(type, m, body); + default: + throw new FormatException(); + } + } + + private BdfMessageContext validateInviteMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 4); + byte[] previousMessageId = body.getOptionalRaw(1); + checkLength(previousMessageId, UniqueId.LENGTH); + BdfList descriptor = body.getList(2); + GroupId groupId = validateDescriptor(descriptor); + String msg = body.getOptionalString(3); + checkLength(msg, 1, MAX_INVITATION_MESSAGE_LENGTH); + + BdfDictionary meta = messageEncoder + .encodeMetadata(INVITE, groupId, m.getTimestamp(), false, false, + false, false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + protected abstract GroupId validateDescriptor(BdfList descriptor) + throws FormatException; + + private BdfMessageContext validateNonInviteMessage(MessageType type, + Message m, BdfList body) throws FormatException { + checkSize(body, 3); + byte[] groupId = body.getRaw(1); + checkLength(groupId, UniqueId.LENGTH); + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + BdfDictionary meta = messageEncoder + .encodeMetadata(type, new GroupId(groupId), m.getTimestamp(), + false, false, false, false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java index 52887134cf3c0945aac3d2ef02a4d5bc1bdfb7b2..3772e87fc52eb281d29181351e8877dc70b14386 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java @@ -3,341 +3,310 @@ package org.briarproject.briar.sharing; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.client.BdfMessageContext; -import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.ValidatorTestCase; -import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.forum.Forum; +import org.briarproject.briar.api.forum.ForumFactory; +import org.jmock.Expectations; import org.junit.Test; +import java.util.Collection; + import javax.annotation.Nullable; -import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME; -import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT; +import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; -import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG; -import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL; import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; -import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID; import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION; -import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE; -import static org.briarproject.briar.api.sharing.SharingConstants.TIME; -import static org.briarproject.briar.api.sharing.SharingConstants.TYPE; +import static org.briarproject.briar.sharing.MessageType.ABORT; +import static org.briarproject.briar.sharing.MessageType.ACCEPT; +import static org.briarproject.briar.sharing.MessageType.DECLINE; +import static org.briarproject.briar.sharing.MessageType.INVITE; +import static org.briarproject.briar.sharing.MessageType.LEAVE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class ForumSharingValidatorTest extends ValidatorTestCase { - private final SessionId sessionId = new SessionId(TestUtils.getRandomId()); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final ForumFactory forumFactory = context.mock(ForumFactory.class); + private final ForumSharingValidator v = + new ForumSharingValidator(messageEncoder, clientHelper, + metadataEncoder, clock, forumFactory); + + private final MessageId previousMsgId = new MessageId(getRandomId()); private final String forumName = TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH); private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH); + private final Forum forum = new Forum(group, forumName, salt); + private final BdfList descriptor = BdfList.of(forumName, salt); private final String content = TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH); @Test public void testAcceptsInvitationWithContent() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectCreateForum(forumName); + expectEncodeMetadata(INVITE); + BdfMessageContext messageContext = v.validateMessage(message, group, + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, + content)); + assertExpectedContext(messageContext, previousMsgId); + } + + @Test + public void testAcceptsInvitationWithNullContent() throws Exception { + expectCreateForum(forumName); + expectEncodeMetadata(INVITE); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, content)); - assertExpectedContextForInvitation(messageContext, forumName, content); + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null)); + assertExpectedContext(messageContext, previousMsgId); } @Test - public void testAcceptsInvitationWithoutContent() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + public void testAcceptsInvitationWithNullPreviousMsgId() throws Exception { + expectCreateForum(forumName); + expectEncodeMetadata(INVITE); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt)); - assertExpectedContextForInvitation(messageContext, forumName, null); + BdfList.of(INVITE.getValue(), null, descriptor, null)); + assertExpectedContext(messageContext, null); } @Test public void testAcceptsAccept() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectEncodeMetadata(ACCEPT); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ACCEPT, sessionId)); - assertExpectedContext(messageContext, SHARE_MSG_TYPE_ACCEPT); + BdfList.of(ACCEPT.getValue(), groupId, previousMsgId)); + assertExpectedContext(messageContext, previousMsgId); } @Test public void testAcceptsDecline() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectEncodeMetadata(DECLINE); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_DECLINE, sessionId)); - assertExpectedContext(messageContext, SHARE_MSG_TYPE_DECLINE); + BdfList.of(DECLINE.getValue(), groupId, previousMsgId)); + assertExpectedContext(messageContext, previousMsgId); } @Test public void testAcceptsLeave() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectEncodeMetadata(LEAVE); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_LEAVE, sessionId)); - assertExpectedContext(messageContext, SHARE_MSG_TYPE_LEAVE); + BdfList.of(LEAVE.getValue(), groupId, previousMsgId)); + assertExpectedContext(messageContext, previousMsgId); } @Test public void testAcceptsAbort() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectEncodeMetadata(ABORT); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId)); - assertExpectedContext(messageContext, SHARE_MSG_TYPE_ABORT); + BdfList.of(ABORT.getValue(), groupId, previousMsgId)); + assertExpectedContext(messageContext, previousMsgId); } @Test(expected = FormatException.class) public void testRejectsNullMessageType() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); - v.validateMessage(message, group, BdfList.of(null, sessionId)); + v.validateMessage(message, group, + BdfList.of(null, groupId, previousMsgId)); } @Test(expected = FormatException.class) public void testRejectsNonLongMessageType() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); - v.validateMessage(message, group, BdfList.of("", sessionId)); + v.validateMessage(message, group, + BdfList.of("", groupId, previousMsgId)); } @Test(expected = FormatException.class) public void testRejectsInvalidMessageType() throws Exception { int invalidMessageType = SHARE_MSG_TYPE_ABORT + 1; - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); v.validateMessage(message, group, - BdfList.of(invalidMessageType, sessionId)); + BdfList.of(invalidMessageType, groupId, previousMsgId)); } @Test(expected = FormatException.class) - public void testRejectsNullSessionId() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + public void testRejectsNullGroupId() throws Exception { v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ABORT, null)); + BdfList.of(ABORT.getValue(), null, previousMsgId)); } @Test(expected = FormatException.class) public void testRejectsNonRawSessionId() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); v.validateMessage(message, group, BdfList.of(SHARE_MSG_TYPE_ABORT, 123)); } @Test(expected = FormatException.class) public void testRejectsTooShortSessionId() throws Exception { - byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId)); + BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId)); } @Test(expected = FormatException.class) public void testRejectsTooLongSessionId() throws Exception { - byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId)); + BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId)); } @Test(expected = FormatException.class) public void testRejectsTooShortBodyForAbort() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); - v.validateMessage(message, group, BdfList.of(SHARE_MSG_TYPE_ABORT)); + v.validateMessage(message, group, + BdfList.of(ABORT.getValue(), groupId)); } @Test(expected = FormatException.class) public void testRejectsTooLongBodyForAbort() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId, 123)); + BdfList.of(SHARE_MSG_TYPE_ABORT, groupId, previousMsgId, 123)); } @Test(expected = FormatException.class) public void testRejectsTooShortBodyForInvitation() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName)); + BdfList.of(INVITE.getValue(), groupId, forumName)); } @Test(expected = FormatException.class) public void testRejectsTooLongBodyForInvitation() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, content, 123)); + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null, + 123)); } @Test(expected = FormatException.class) public void testRejectsNullForumName() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(null, salt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, null, - salt, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsNonStringForumName() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(123, salt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, 123, - salt, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsTooShortForumName() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of("", salt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, "", - salt, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test public void testAcceptsMinLengthForumName() throws Exception { String shortForumName = TestUtils.getRandomString(1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); - BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, shortForumName, - salt, content)); - assertExpectedContextForInvitation(messageContext, shortForumName, - content); + BdfList validDescriptor = BdfList.of(shortForumName, salt); + expectCreateForum(shortForumName); + expectEncodeMetadata(INVITE); + v.validateMessage(message, group, + BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsTooLongForumName() throws Exception { String invalidForumName = TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH + 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(invalidForumName, salt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, - invalidForumName, salt, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsNullSalt() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(forumName, null); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - null, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsNonRawSalt() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(forumName, 123); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - 123, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsTooShortSalt() throws Exception { byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH - 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - invalidSalt, content)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsTooLongSalt() throws Exception { byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH + 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - invalidSalt, content)); - } - - @Test(expected = FormatException.class) - public void testRejectsNullContent() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); - v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, null)); + BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor, + null)); } @Test(expected = FormatException.class) public void testRejectsNonStringContent() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectCreateForum(forumName); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, 123)); + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, + 123)); } @Test public void testAcceptsMinLengthContent() throws Exception { - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectCreateForum(forumName); + expectEncodeMetadata(INVITE); BdfMessageContext messageContext = v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, "")); - assertExpectedContextForInvitation(messageContext, forumName, ""); + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, "1")); + assertExpectedContext(messageContext, previousMsgId); } @Test(expected = FormatException.class) public void testRejectsTooLongContent() throws Exception { String invalidContent = TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1); - ForumSharingValidator v = new ForumSharingValidator(clientHelper, - metadataEncoder, clock); + expectCreateForum(forumName); v.validateMessage(message, group, - BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName, - salt, invalidContent)); + BdfList.of(INVITE.getValue(), previousMsgId, descriptor, + invalidContent)); } - private void assertExpectedContextForInvitation( - BdfMessageContext messageContext, String forumName, - @Nullable String content) throws FormatException { - BdfDictionary meta = messageContext.getDictionary(); - if (content == null) { - assertEquals(6, meta.size()); - } else { - assertEquals(7, meta.size()); - assertEquals(content, meta.getString(INVITATION_MSG)); - } - assertEquals(forumName, meta.getString(FORUM_NAME)); - assertEquals(salt, meta.getRaw(FORUM_SALT)); - assertEquals(SHARE_MSG_TYPE_INVITATION, meta.getLong(TYPE).intValue()); - assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID)); - assertFalse(meta.getBoolean(LOCAL)); - assertEquals(timestamp, meta.getLong(TIME).longValue()); - assertEquals(0, messageContext.getDependencies().size()); + private void expectCreateForum(final String name) { + context.checking(new Expectations() {{ + oneOf(forumFactory).createForum(name, salt); + will(returnValue(forum)); + }}); + } + + private void expectEncodeMetadata(final MessageType type) { + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMetadata(type, groupId, timestamp, false, false, + false, false); + }}); } private void assertExpectedContext(BdfMessageContext messageContext, - int type) throws FormatException { - BdfDictionary meta = messageContext.getDictionary(); - assertEquals(4, meta.size()); - assertEquals(type, meta.getLong(TYPE).intValue()); - assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID)); - assertFalse(meta.getBoolean(LOCAL)); - assertEquals(timestamp, meta.getLong(TIME).longValue()); - assertEquals(0, messageContext.getDependencies().size()); + @Nullable MessageId previousMsgId) throws FormatException { + Collection<MessageId> dependencies = messageContext.getDependencies(); + if (previousMsgId == null) { + assertTrue(dependencies.isEmpty()); + } else { + assertEquals(1, dependencies.size()); + assertTrue(dependencies.contains(previousMsgId)); + } } + }