diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 7e27d74685bdf55eb9fb23d27981b029d97796ad..76f5b6b3747b6aebb961d1907e2561166d2835ce 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -9,7 +9,6 @@ import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.nullsafety.NotNullByDefault; -import org.briarproject.api.privategroup.MessageType; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.invitation.GroupInvitationFactory; @@ -79,17 +78,14 @@ class GroupMessageValidator extends BdfMessageValidator { Author member = authorFactory.createAuthor(memberName, memberPublicKey); BdfMessageContext c; - switch (MessageType.valueOf(type)) { - case JOIN: - c = validateJoin(m, g, body, member); - addMessageMetadata(c, member, m.getTimestamp()); - break; - case POST: - c = validatePost(m, g, body, member); - addMessageMetadata(c, member, m.getTimestamp()); - break; - default: - throw new InvalidMessageException("Unknown Message Type"); + if (type == JOIN.getInt()) { + c = validateJoin(m, g, body, member); + addMessageMetadata(c, member, m.getTimestamp()); + } else if (type == POST.getInt()) { + c = validatePost(m, g, body, member); + addMessageMetadata(c, member, m.getTimestamp()); + } else { + throw new InvalidMessageException("Unknown Message Type"); } c.getDictionary().put(KEY_TYPE, type); return c; @@ -133,8 +129,9 @@ class GroupMessageValidator extends BdfMessageValidator { .createInviteToken(creator.getId(), member.getId(), pg.getId(), inviteTimestamp); try { - clientHelper.verifySignature(SIGNING_LABEL_INVITE, creatorSignature, - creator.getPublicKey(), token); + clientHelper + .verifySignature(SIGNING_LABEL_INVITE, creatorSignature, + creator.getPublicKey(), token); } catch (GeneralSecurityException e) { throw new InvalidMessageException(e); } @@ -180,7 +177,7 @@ class GroupMessageValidator extends BdfMessageValidator { // content (string) String content = body.getString(5); - checkLength(content, 0, MAX_GROUP_POST_BODY_LENGTH); + checkLength(content, 1, MAX_GROUP_POST_BODY_LENGTH); // signature (raw) // a signature with the member's private key over a list with 7 elements diff --git a/briar-tests/src/org/briarproject/ValidatorTestCase.java b/briar-tests/src/org/briarproject/ValidatorTestCase.java index 4e5a3b5e62392abe2cd2333bf73e164c5eb24e57..f7a7693fdae94e2d1e3adb038f4b6517de899f05 100644 --- a/briar-tests/src/org/briarproject/ValidatorTestCase.java +++ b/briar-tests/src/org/briarproject/ValidatorTestCase.java @@ -2,6 +2,7 @@ package org.briarproject; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; @@ -16,6 +17,8 @@ public abstract class ValidatorTestCase extends BriarMockTestCase { protected final MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); protected final Clock clock = context.mock(Clock.class); + protected final AuthorFactory authorFactory = + context.mock(AuthorFactory.class); protected final MessageId messageId = new MessageId(TestUtils.getRandomId()); diff --git a/briar-tests/src/org/briarproject/privategroup/GroupMessageValidatorTest.java b/briar-tests/src/org/briarproject/privategroup/GroupMessageValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cff74d31d34d043860c821705e297114a06cb253 --- /dev/null +++ b/briar-tests/src/org/briarproject/privategroup/GroupMessageValidatorTest.java @@ -0,0 +1,648 @@ +package org.briarproject.privategroup; + +import org.briarproject.ValidatorTestCase; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.BdfMessageContext; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.privategroup.MessageType; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; +import org.briarproject.api.privategroup.invitation.GroupInvitationFactory; +import org.briarproject.api.sync.InvalidMessageException; +import org.briarproject.api.sync.MessageId; +import org.jmock.Expectations; +import org.junit.Test; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static org.briarproject.TestUtils.getRandomBytes; +import static org.briarproject.TestUtils.getRandomId; +import static org.briarproject.TestUtils.getRandomString; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.api.privategroup.GroupMessageFactory.SIGNING_LABEL_JOIN; +import static org.briarproject.api.privategroup.GroupMessageFactory.SIGNING_LABEL_POST; +import static org.briarproject.api.privategroup.MessageType.JOIN; +import static org.briarproject.api.privategroup.MessageType.POST; +import static org.briarproject.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; +import static org.briarproject.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE; +import static org.briarproject.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME; +import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY; +import static org.briarproject.privategroup.GroupConstants.KEY_PARENT_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID; +import static org.briarproject.privategroup.GroupConstants.KEY_READ; +import static org.briarproject.privategroup.GroupConstants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.GroupConstants.KEY_TYPE; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GroupMessageValidatorTest extends ValidatorTestCase { + + private final PrivateGroupFactory privateGroupFactory = + context.mock(PrivateGroupFactory.class); + private final GroupInvitationFactory groupInvitationFactory = + context.mock(GroupInvitationFactory.class); + + private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); + private final String memberName = getRandomString(MAX_AUTHOR_NAME_LENGTH); + private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final byte[] memberKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final byte[] creatorSignature = + getRandomBytes(MAX_SIGNATURE_LENGTH); + private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH); + private final Author member = + new Author(new AuthorId(getRandomId()), memberName, memberKey); + private final Author creator = + new Author(new AuthorId(getRandomId()), creatorName, creatorKey); + private final long inviteTimestamp = 42L; + private final PrivateGroup privateGroup = new PrivateGroup(group, + getRandomString(MAX_GROUP_NAME_LENGTH), creator, + getRandomBytes(GROUP_SALT_LENGTH)); + private final BdfList token = BdfList.of("token"); + private final MessageId parentId = new MessageId(getRandomId()); + private final MessageId previousMsgId = new MessageId(getRandomId()); + private final String postContent = + getRandomString(MAX_GROUP_POST_BODY_LENGTH); + + private final GroupMessageValidator validator = + new GroupMessageValidator(privateGroupFactory, clientHelper, + metadataEncoder, clock, authorFactory, + groupInvitationFactory); + + // JOIN message + + @Test(expected = FormatException.class) + public void testRejectsTooShortJoinMessage() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongJoinMessage() throws Exception { + expectCreateAuthor(creator); + BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null, + signature, ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithTooShortMemberName() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), "", memberKey, null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithTooLongMemberName() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), + getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey, null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithNullMemberName() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), null, memberKey, null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithNonStringMemberName() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), getRandomBytes(5), memberKey, + null, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithTooShortMemberKey() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), memberName, new byte[0], null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithTooLongMemberKey() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), memberName, + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), null, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithNoullMemberKey() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), memberName, null, null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithNonRawMemberKey() throws Exception { + BdfList body = BdfList.of(JOIN.getInt(), memberName, "not raw", null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithNonListInvitation() throws Exception { + expectCreateAuthor(creator); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, + "not a list", signature); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsCreatorJoin() throws Exception { + expectJoinMessage(creator, null, true, true); + BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, + null, signature); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertExpectedMessageContext(messageContext, JOIN, creator, + Collections.<MessageId>emptyList()); + assertTrue(messageContext.getDictionary() + .getBoolean(KEY_INITIAL_JOIN_MSG)); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMemberJoinWithNullInvitation() throws Exception { + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, null, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithTooShortInvitation() throws Exception { + BdfList invite = BdfList.of(inviteTimestamp); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithTooLongInvitation() throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, creatorSignature, ""); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMemberJoinWithEqualInvitationTime() + throws Exception { + BdfList invite = BdfList.of(message.getTimestamp(), creatorSignature); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMemberJoinWithLaterInvitationTime() + throws Exception { + BdfList invite = BdfList.of(message.getTimestamp() + 1, + creatorSignature); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithNullInvitationTime() + throws Exception { + BdfList invite = BdfList.of(null, creatorSignature); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithNonLongInvitationTime() + throws Exception { + BdfList invite = BdfList.of("not long", creatorSignature); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithTooShortCreatorSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, new byte[0]); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinWithTooLongCreatorSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, + getRandomBytes(MAX_SIGNATURE_LENGTH + 1)); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithNullCreatorSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, null); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsMemberJoinWithNonRawCreatorSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, "not raw"); + expectCreateAuthor(member); + expectParsePrivateGroup(); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMemberJoinWithInvalidCreatorSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, creatorSignature); + expectJoinMessage(member, invite, false, true); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMemberJoinWithInvalidMemberSignature() + throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, creatorSignature); + expectJoinMessage(member, invite, true, false); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsMemberJoin() throws Exception { + BdfList invite = BdfList.of(inviteTimestamp, creatorSignature); + expectJoinMessage(member, invite, true, true); + BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite, + signature); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertExpectedMessageContext(messageContext, JOIN, member, + Collections.<MessageId>emptyList()); + assertFalse(messageContext.getDictionary() + .getBoolean(KEY_INITIAL_JOIN_MSG)); + } + + private void expectCreateAuthor(final Author member) { + context.checking(new Expectations() {{ + oneOf(authorFactory).createAuthor(member.getName(), + member.getPublicKey()); + will(returnValue(member)); + }}); + } + + private void expectParsePrivateGroup() throws Exception { + context.checking(new Expectations() {{ + oneOf(privateGroupFactory).parsePrivateGroup(group); + will(returnValue(privateGroup)); + }}); + } + + private void expectJoinMessage(final Author member, final BdfList invite, + final boolean creatorSigValid, final boolean memberSigValid) + throws Exception { + final BdfList signed = BdfList.of(group.getId(), message.getTimestamp(), + JOIN.getInt(), member.getName(), member.getPublicKey(), invite); + expectCreateAuthor(member); + expectParsePrivateGroup(); + context.checking(new Expectations() {{ + if (invite != null) { + oneOf(groupInvitationFactory).createInviteToken(creator.getId(), + member.getId(), privateGroup.getId(), inviteTimestamp); + will(returnValue(token)); + oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE, + creatorSignature, creatorKey, token); + if (!memberSigValid) + will(throwException(new GeneralSecurityException())); + } + if (memberSigValid) { + oneOf(clientHelper).verifySignature(SIGNING_LABEL_JOIN, + signature, member.getPublicKey(), signed); + if (!creatorSigValid) + will(throwException(new GeneralSecurityException())); + } + }}); + } + + // POST Message + + @Test(expected = FormatException.class) + public void testRejectsTooShortPost() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongPost() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, signature, ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortMemberName() throws Exception { + BdfList body = BdfList.of(POST.getInt(), "", memberKey, parentId, + previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongMemberName() throws Exception { + BdfList body = BdfList.of(POST.getInt(), + getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey, + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNullMemberName() throws Exception { + BdfList body = BdfList.of(POST.getInt(), null, memberKey, + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonStringMemberName() throws Exception { + BdfList body = BdfList.of(POST.getInt(), getRandomBytes(5), memberKey, + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortMemberKey() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, new byte[0], + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongMemberKey() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), parentId, + previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNullMemberKey() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, null, + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonRawMemberKey() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, "not raw", + parentId, previousMsgId, postContent, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortParentId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + getRandomBytes(MessageId.LENGTH - 1), previousMsgId, + postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongParentId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + getRandomBytes(MessageId.LENGTH + 1), previousMsgId, + postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonRawParentId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + "not raw", previousMsgId, postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortPreviousMsgId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, getRandomBytes(MessageId.LENGTH - 1), postContent, + signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongPreviousMsgId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, getRandomBytes(MessageId.LENGTH + 1), postContent, + signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNullPreviousMsgId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, null, postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonRawPreviousMsgId() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, "not raw", postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortContent() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, "", signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongContent() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, + getRandomString(MAX_GROUP_POST_BODY_LENGTH + 1), signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNullContent() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, null, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonStringContent() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, getRandomBytes(5), signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooShortSignature() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, new byte[0]); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithTooLongSignature() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, + getRandomBytes(MAX_SIGNATURE_LENGTH + 1)); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNullSignature() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent,null); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPostWithNonRawSignature() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, "not raw"); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsPostWithInvalidSignature() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, signature); + expectPostMessage(member, parentId, false); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsPost() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, + parentId, previousMsgId, postContent, signature); + expectPostMessage(member, parentId, true); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertExpectedMessageContext(messageContext, POST, member, + Arrays.asList(parentId, previousMsgId)); + assertArrayEquals(previousMsgId.getBytes(), + messageContext.getDictionary().getRaw(KEY_PREVIOUS_MSG_ID)); + assertArrayEquals(parentId.getBytes(), + messageContext.getDictionary().getRaw(KEY_PARENT_MSG_ID)); + } + + @Test + public void testAcceptsTopLevelPost() throws Exception { + BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, null, + previousMsgId, postContent, signature); + expectPostMessage(member, null, true); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertExpectedMessageContext(messageContext, POST, member, + Collections.singletonList(previousMsgId)); + assertArrayEquals(previousMsgId.getBytes(), + messageContext.getDictionary().getRaw(KEY_PREVIOUS_MSG_ID)); + assertFalse( + messageContext.getDictionary().containsKey(KEY_PARENT_MSG_ID)); + } + + private void expectPostMessage(final Author member, + final MessageId parentId, final boolean sigValid) throws Exception { + final BdfList signed = BdfList.of(group.getId(), message.getTimestamp(), + POST.getInt(), member.getName(), member.getPublicKey(), + parentId == null ? null : parentId.getBytes(), + previousMsgId.getBytes(), postContent); + expectCreateAuthor(member); + context.checking(new Expectations() {{ + oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature, + member.getPublicKey(), signed); + if (!sigValid) will(throwException(new GeneralSecurityException())); + }}); + } + + private void assertExpectedMessageContext(BdfMessageContext c, + MessageType type, Author member, + Collection<MessageId> dependencies) throws FormatException { + BdfDictionary d = c.getDictionary(); + assertEquals(type.getInt(), d.getLong(KEY_TYPE).intValue()); + assertEquals(message.getTimestamp(), + d.getLong(KEY_TIMESTAMP).longValue()); + assertFalse(d.getBoolean(KEY_READ)); + assertEquals(member.getId().getBytes(), d.getRaw(KEY_MEMBER_ID)); + assertEquals(member.getName(), d.getString(KEY_MEMBER_NAME)); + assertEquals(member.getPublicKey(), d.getRaw(KEY_MEMBER_PUBLIC_KEY)); + assertEquals(dependencies, c.getDependencies()); + } + + @Test(expected = InvalidMessageException.class) + public void testRejectsMessageWithUnknownType() throws Exception { + BdfList body = BdfList.of(POST.getInt() + 1, memberName, memberKey, + parentId, previousMsgId, postContent, signature); + expectCreateAuthor(member); + validator.validateMessage(message, group, body); + } +} diff --git a/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationValidatorTest.java b/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..76ed641b4219ef855da1143afc41eb669f9af59e --- /dev/null +++ b/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationValidatorTest.java @@ -0,0 +1,582 @@ +package org.briarproject.privategroup.invitation; + +import org.briarproject.ValidatorTestCase; +import org.briarproject.api.FormatException; +import org.briarproject.api.UniqueId; +import org.briarproject.api.clients.BdfMessageContext; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.jmock.Expectations; +import org.junit.Test; + +import java.security.GeneralSecurityException; + +import static org.briarproject.TestUtils.getRandomBytes; +import static org.briarproject.TestUtils.getRandomId; +import static org.briarproject.TestUtils.getRandomString; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; +import static org.briarproject.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE; +import static org.briarproject.privategroup.invitation.MessageType.ABORT; +import static org.briarproject.privategroup.invitation.MessageType.INVITE; +import static org.briarproject.privategroup.invitation.MessageType.JOIN; +import static org.briarproject.privategroup.invitation.MessageType.LEAVE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class GroupInvitationValidatorTest extends ValidatorTestCase { + + private final PrivateGroupFactory privateGroupFactory = + context.mock(PrivateGroupFactory.class); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + + private final String groupName = getRandomString(MAX_GROUP_NAME_LENGTH); + private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); + private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final Author creator = + new Author(new AuthorId(getRandomId()), creatorName, creatorKey); + private final byte[] salt = getRandomBytes(GROUP_SALT_LENGTH); + private final PrivateGroup privateGroup = + new PrivateGroup(group, groupName, creator, salt); + private final String inviteText = + getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH); + private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH); + private final BdfDictionary meta = + BdfDictionary.of(new BdfEntry("meta", "data")); + private final MessageId previousMessageId = new MessageId(getRandomId()); + + private final GroupInvitationValidator validator = + new GroupInvitationValidator(clientHelper, metadataEncoder, + clock, authorFactory, privateGroupFactory, messageEncoder); + + // INVITE Message + + @Test(expected = FormatException.class) + public void testRejectsTooShortInviteMessage() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongInviteMessage() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, signature, ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortGroupName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), "", creatorName, + creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongGroupName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), + getRandomString(MAX_GROUP_NAME_LENGTH + 1), creatorName, + creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNullGroupName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), null, creatorName, + creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonStringGroupName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), getRandomBytes(5), + creatorName, creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortCreatorName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, "", creatorKey, + salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongCreatorName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, + getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), creatorKey, salt, + inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNullCreatorName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, null, + creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonStringCreatorName() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, + getRandomBytes(5), creatorKey, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortCreatorKey() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + new byte[0], salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongCreatorKey() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), salt, inviteText, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNullCreatorKey() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + null, salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonRawCreatorKey() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + "not raw", salt, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortGroupSalt() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, getRandomBytes(GROUP_SALT_LENGTH - 1), inviteText, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongGroupSalt() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, getRandomBytes(GROUP_SALT_LENGTH + 1), inviteText, + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNullGroupSalt() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, null, inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonRawGroupSalt() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, "not raw", inviteText, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortContent() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, "", signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongContent() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, + getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH + 1), + signature); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsInviteMessageWithNullContent() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, null, signature); + expectInviteMessage(false); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonStringContent() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, getRandomBytes(5), signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooShortSignature() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, new byte[0]); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithTooLongSignature() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, + getRandomBytes(MAX_SIGNATURE_LENGTH + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNullSignature() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithNonRawSignature() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, "not raw"); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInviteMessageWithInvalidSignature() + throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, null, signature); + expectInviteMessage(true); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsValidInviteMessage() throws Exception { + BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName, + creatorKey, salt, inviteText, signature); + expectInviteMessage(false); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertTrue(messageContext.getDependencies().isEmpty()); + assertEquals(meta, messageContext.getDictionary()); + } + + private void expectInviteMessage(final boolean exception) throws Exception { + final BdfList signed = BdfList.of(message.getTimestamp(), + message.getGroupId(), privateGroup.getId()); + context.checking(new Expectations() {{ + oneOf(authorFactory).createAuthor(creatorName, creatorKey); + will(returnValue(creator)); + oneOf(privateGroupFactory).createPrivateGroup(groupName, creator, + salt); + will(returnValue(privateGroup)); + oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE, signature, + creatorKey, signed); + if (exception) { + will(throwException(new GeneralSecurityException())); + } else { + oneOf(messageEncoder).encodeMetadata(INVITE, + message.getGroupId(), message.getTimestamp(), false, + false, false, false); + will(returnValue(meta)); + } + }}); + } + + // JOIN Message + + @Test(expected = FormatException.class) + public void testRejectsTooShortJoinMessage() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongJoinMessage() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), + previousMessageId, ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithTooShortGroupId() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), + getRandomBytes(GroupId.LENGTH - 1), previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithTooLongGroupId() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), + getRandomBytes(GroupId.LENGTH + 1), previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithNullGroupId() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), null, previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithNonRawGroupId() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), "not raw", + previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithTooShortPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), + getRandomBytes(UniqueId.LENGTH - 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithTooLongPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), + getRandomBytes(UniqueId.LENGTH + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsJoinMessageWithNonRawPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), + "not raw"); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsJoinMessageWithNullPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), null); + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeMetadata(JOIN, message.getGroupId(), + message.getTimestamp(), false, false, false, false); + will(returnValue(meta)); + }}); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertEquals(0, messageContext.getDependencies().size()); + assertEquals(meta, messageContext.getDictionary()); + } + + @Test + public void testAcceptsValidJoinMessage() throws Exception { + BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), + previousMessageId); + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeMetadata(JOIN, message.getGroupId(), + message.getTimestamp(), false, false, false, false); + will(returnValue(meta)); + }}); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertEquals(1, messageContext.getDependencies().size()); + assertEquals(previousMessageId, + messageContext.getDependencies().iterator().next()); + assertEquals(meta, messageContext.getDictionary()); + } + + // LEAVE message + + @Test(expected = FormatException.class) + public void testRejectsTooShortLeaveMessage() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongLeaveMessage() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), + previousMessageId, ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithTooShortGroupId() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), + getRandomBytes(GroupId.LENGTH - 1), previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithTooLongGroupId() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), + getRandomBytes(GroupId.LENGTH + 1), previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithNullGroupId() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), null, previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithNonRawGroupId() throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), "not raw", + previousMessageId); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithTooShortPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), + getRandomBytes(UniqueId.LENGTH - 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithTooLongPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), + getRandomBytes(UniqueId.LENGTH + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsLeaveMessageWithNonRawPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), + "not raw"); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsLeaveMessageWithNullPreviousMessageId() + throws Exception { + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), null); + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeMetadata(LEAVE, message.getGroupId(), + message.getTimestamp(), false, false, false, false); + will(returnValue(meta)); + }}); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertEquals(0, messageContext.getDependencies().size()); + assertEquals(meta, messageContext.getDictionary()); + } + + @Test + public void testAcceptsValidLeaveMessage() throws Exception { + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeMetadata(LEAVE, message.getGroupId(), + message.getTimestamp(), false, false, false, false); + will(returnValue(meta)); + }}); + BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), + previousMessageId); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertEquals(1, messageContext.getDependencies().size()); + assertEquals(previousMessageId, + messageContext.getDependencies().iterator().next()); + assertEquals(meta, messageContext.getDictionary()); + } + + // ABORT message + + @Test(expected = FormatException.class) + public void testRejectsTooShortAbortMessage() throws Exception { + BdfList body = BdfList.of(ABORT.getValue()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongAbortMessage() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), privateGroup.getId(), ""); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsAbortMessageWithTooShortGroupId() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), + getRandomBytes(GroupId.LENGTH - 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsAbortMessageWithTooLongGroupId() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), + getRandomBytes(GroupId.LENGTH + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsAbortMessageWithNullGroupId() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsAbortMessageWithNonRawGroupId() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), "not raw"); + validator.validateMessage(message, group, body); + } + + @Test + public void testAcceptsValidAbortMessage() throws Exception { + context.checking(new Expectations() {{ + oneOf(messageEncoder).encodeMetadata(ABORT, message.getGroupId(), + message.getTimestamp(), false, false, false, false); + will(returnValue(meta)); + }}); + BdfList body = BdfList.of(ABORT.getValue(), privateGroup.getId()); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + assertEquals(0, messageContext.getDependencies().size()); + assertEquals(meta, messageContext.getDictionary()); + } + + @Test(expected = FormatException.class) + public void testRejectsMessageWithUnknownType() throws Exception { + BdfList body = BdfList.of(ABORT.getValue() + 1); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyMessage() throws Exception { + BdfList body = new BdfList(); + validator.validateMessage(message, group, body); + } + +}