Move all unit tests to their modules and remove briar-tests

This commit is contained in:
Torsten Grote
2016-12-12 17:06:30 -02:00
parent 32be148c7a
commit 1081a08ea9
130 changed files with 441 additions and 433 deletions

View File

@@ -2,8 +2,7 @@ package org.briarproject.briar;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
@@ -60,11 +59,11 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import static junit.framework.Assert.assertNotNull;
import static org.briarproject.TestPluginConfigModule.MAX_LATENCY;
import static org.briarproject.TestUtils.getSecretKey;
import static org.briarproject.bramble.TestUtils.getSecretKey;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.briar.TestPluginConfigModule.MAX_LATENCY;
import static org.junit.Assert.assertTrue;
@MethodsNotNullByDefault

View File

@@ -1,8 +1,7 @@
package org.briarproject.briar;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestPluginConfigModule;
import org.briarproject.TestSeedProviderModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.TestSeedProviderModule;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -0,0 +1,11 @@
package org.briarproject.briar;
import org.briarproject.bramble.BrambleTestCase;
public abstract class BriarTestCase extends BrambleTestCase {
public BriarTestCase() {
super();
}
}

View File

@@ -0,0 +1,28 @@
package org.briarproject.briar;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import static org.junit.Assert.assertEquals;
public class BriarTestUtils {
public static void assertGroupCount(MessageTracker tracker, GroupId g,
long msgCount, long unreadCount, long latestMsgTime)
throws DbException {
GroupCount groupCount = tracker.getGroupCount(g);
assertEquals(msgCount, groupCount.getMsgCount());
assertEquals(unreadCount, groupCount.getUnreadCount());
assertEquals(latestMsgTime, groupCount.getLatestMsgTime());
}
public static void assertGroupCount(MessageTracker tracker, GroupId g,
long msgCount, long unreadCount) throws DbException {
GroupCount c1 = tracker.getGroupCount(g);
assertEquals(msgCount, c1.getMsgCount());
assertEquals(unreadCount, c1.getUnreadCount());
}
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.briar;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import java.util.concurrent.atomic.AtomicReference;
public class CaptureArgumentAction<T> implements Action {
private final AtomicReference<T> captured;
private final Class<T> capturedClass;
private final int index;
public CaptureArgumentAction(AtomicReference<T> captured,
Class<T> capturedClass, int index) {
this.captured = captured;
this.capturedClass = capturedClass;
this.index = index;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
captured.set(capturedClass.cast(invocation.getParameter(index)));
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("captures an argument");
}
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.briar;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Nullable;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class TestLifecycleModule {
@Provides
LifecycleManager provideLifecycleManager() {
@NotNullByDefault
LifecycleManager lifecycleManager = new LifecycleManager() {
@Override
public void registerService(Service s) {
}
@Override
public void registerClient(Client c) {
}
@Override
public void registerForShutdown(ExecutorService e) {
}
@Override
public StartResult startServices(@Nullable String nickname) {
return StartResult.SUCCESS;
}
@Override
public void stopServices() {
}
@Override
public void waitForDatabase() throws InterruptedException {
}
@Override
public void waitForStartup() throws InterruptedException {
}
@Override
public void waitForShutdown() throws InterruptedException {
}
};
return lifecycleManager;
}
@Provides
ShutdownManager provideShutdownManager() {
@NotNullByDefault
ShutdownManager shutdownManager = new ShutdownManager() {
@Override
public int addShutdownHook(Runnable hook) {
return 0;
}
@Override
public boolean removeShutdownHook(int handle) {
return true;
}
};
return shutdownManager;
}
@Provides
@IoExecutor
@Singleton
Executor provideIoExecutor() {
return Executors.newCachedThreadPool();
}
}

View File

@@ -0,0 +1,62 @@
package org.briarproject.briar;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import java.util.Collection;
import java.util.Collections;
import javax.annotation.Nullable;
import dagger.Module;
import dagger.Provides;
@Module
public class TestPluginConfigModule {
public static final TransportId TRANSPORT_ID = new TransportId("id");
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
@NotNullByDefault
private final SimplexPluginFactory simplex = new SimplexPluginFactory() {
@Override
public TransportId getId() {
return TRANSPORT_ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Override
@Nullable
public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
return null;
}
};
@Provides
PluginConfig providePluginConfig() {
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@Override
public Collection<DuplexPluginFactory> getDuplexFactories() {
return Collections.emptyList();
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return Collections.singletonList(simplex);
}
};
return pluginConfig;
}
}

View File

@@ -0,0 +1,311 @@
package org.briarproject.briar.blog;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
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.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogFactory;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogPostHeader;
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
import static org.briarproject.briar.api.blog.MessageType.POST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BlogManagerImplTest extends BriarTestCase {
private final Mockery context = new Mockery();
private final BlogManagerImpl blogManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final BlogFactory blogFactory = context.mock(BlogFactory.class);
private final Blog blog1, blog2;
private final Message message;
private final MessageId messageId;
public BlogManagerImplTest() {
MetadataParser metadataParser = context.mock(MetadataParser.class);
BlogPostFactory blogPostFactory = context.mock(BlogPostFactory.class);
blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
metadataParser, contactManager, blogFactory, blogPostFactory);
blog1 = createBlog();
blog2 = createBlog();
messageId = new MessageId(getRandomId());
message = new Message(messageId, blog1.getId(), 42, getRandomBytes(42));
}
@Test
public void testCreateLocalState() throws DbException {
final Transaction txn = new Transaction(null, false);
final ContactId contactId = new ContactId(0);
Contact contact = new Contact(contactId, blog2.getAuthor(),
blog1.getAuthor().getId(), true, true);
final Collection<Contact> contacts = Collections.singletonList(contact);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).addGroup(txn, blog1.getGroup());
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(blogFactory).createBlog(blog2.getAuthor());
will(returnValue(blog2));
oneOf(db).addGroup(txn, blog2.getGroup());
oneOf(db).setGroupVisibility(txn, contactId, blog2.getId(), SHARED);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).setGroupVisibility(txn, contactId, blog1.getId(), SHARED);
}});
blogManager.createLocalState(txn);
context.assertIsSatisfied();
}
@Test
public void testRemovingContact() throws DbException {
final Transaction txn = new Transaction(null, false);
final ContactId contactId = new ContactId(0);
Contact contact = new Contact(contactId, blog2.getAuthor(),
blog1.getAuthor().getId(), true, true);
context.checking(new Expectations() {{
oneOf(blogFactory).createBlog(blog2.getAuthor());
will(returnValue(blog2));
oneOf(db).removeGroup(txn, blog2.getGroup());
}});
blogManager.removingContact(txn, contact);
context.assertIsSatisfied();
}
@Test
public void testIncomingMessage() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
BdfList list = new BdfList();
BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, 0),
new BdfEntry(KEY_TIME_RECEIVED, 1),
new BdfEntry(KEY_AUTHOR, author),
new BdfEntry(KEY_READ, false)
);
context.checking(new Expectations() {{
oneOf(identityManager)
.getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED));
}});
blogManager.incomingMessage(txn, message, list, meta);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(1, h.getTimeReceived());
assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId());
assertEquals(VERIFIED, h.getAuthorStatus());
assertEquals(blog1.getAuthor(), h.getAuthor());
}
@Test
public void testRemoveBlog() throws Exception {
final Transaction txn = new Transaction(null, false);
checkGetBlogExpectations(txn, false, blog1);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(false));
oneOf(db).removeGroup(txn, blog1.getGroup());
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
blogManager.removeBlog(blog1);
context.assertIsSatisfied();
}
@Test
public void testAddLocalPost() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
final BlogPost post =
new BlogPost(message, null, blog1.getAuthor());
BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
final BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
new BdfEntry(KEY_AUTHOR, authorMeta),
new BdfEntry(KEY_READ, true)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(clientHelper)
.addLocalMessage(txn, message, meta, true);
oneOf(identityManager)
.getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
blogManager.addLocalPost(post);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(message.getTimestamp(), h.getTimeReceived());
assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId());
assertEquals(VERIFIED, h.getAuthorStatus());
assertEquals(blog1.getAuthor(), h.getAuthor());
}
@Test
public void testBlogCanBeRemoved() throws Exception {
// check that own personal blogs can not be removed
final Transaction txn = new Transaction(null, true);
checkGetBlogExpectations(txn, true, blog1);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertFalse(blogManager.canBeRemoved(blog1.getId()));
context.assertIsSatisfied();
// check that blogs of contacts can not be removed
final Transaction txn2 = new Transaction(null, true);
checkGetBlogExpectations(txn2, true, blog1);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn2);
will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn2, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(true));
oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2);
}});
assertFalse(blogManager.canBeRemoved(blog1.getId()));
context.assertIsSatisfied();
// check that blogs can be removed if they don't belong to a contact
final Transaction txn3 = new Transaction(null, true);
checkGetBlogExpectations(txn3, true, blog1);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn3);
will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn3, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(false));
oneOf(db).commitTransaction(txn3);
oneOf(db).endTransaction(txn3);
}});
assertTrue(blogManager.canBeRemoved(blog1.getId()));
context.assertIsSatisfied();
}
private void checkGetBlogExpectations(final Transaction txn,
final boolean readOnly, final Blog blog) throws Exception {
context.checking(new Expectations() {{
oneOf(db).startTransaction(readOnly);
will(returnValue(txn));
oneOf(db).getGroup(txn, blog.getId());
will(returnValue(blog.getGroup()));
oneOf(blogFactory).parseBlog(blog.getGroup());
will(returnValue(blog));
}});
}
private Blog createBlog() {
final GroupId groupId = new GroupId(getRandomId());
final Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
final AuthorId authorId = new AuthorId(getRandomId());
final byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final long created = System.currentTimeMillis();
final LocalAuthor localAuthor =
new LocalAuthor(authorId, "Author", publicKey, privateKey,
created);
return new Blog(group, localAuthor);
}
private BdfDictionary authorToBdfDictionary(Author a) {
return BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, a.getId()),
new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
);
}
}

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.blog;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.BriarIntegrationTest;
@@ -21,7 +21,7 @@ import java.util.Iterator;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static org.briarproject.TestUtils.getRandomString;
import static org.briarproject.bramble.TestUtils.getRandomString;
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
import static org.briarproject.briar.api.blog.MessageType.POST;
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT;

View File

@@ -0,0 +1,270 @@
package org.briarproject.briar.blog;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
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.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupFactory;
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 org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogFactory;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.IOException;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT;
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST;
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
import static org.briarproject.briar.api.blog.MessageType.POST;
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT;
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_POST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class BlogPostValidatorTest extends BriarTestCase {
private final Mockery context = new Mockery();
private final Blog blog;
private final BdfDictionary authorDict;
private final ClientId clientId;
private final byte[] descriptor;
private final Group group;
private final Message message;
private final BlogPostValidator validator;
private final GroupFactory groupFactory = context.mock(GroupFactory.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
private final BlogFactory blogFactory = context.mock(BlogFactory.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final Author author;
private final String body = TestUtils.getRandomString(42);
public BlogPostValidatorTest() {
GroupId groupId = new GroupId(TestUtils.getRandomId());
clientId = BlogManagerImpl.CLIENT_ID;
descriptor = TestUtils.getRandomBytes(42);
group = new Group(groupId, clientId, descriptor);
AuthorId authorId =
new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH));
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
author = new Author(authorId, "Author", publicKey);
authorDict = BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, author.getId()),
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
);
blog = new Blog(group, author);
MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
byte[] raw = TestUtils.getRandomBytes(123);
message = new Message(messageId, group.getId(), timestamp, raw);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
Clock clock = new SystemClock();
validator =
new BlogPostValidator(groupFactory, messageFactory, blogFactory,
clientHelper, metadataEncoder, clock);
context.assertIsSatisfied();
}
@Test
public void testValidateProperBlogPost()
throws IOException, GeneralSecurityException {
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateBlogPostWithoutAttachments()
throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, null, body);
BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary();
}
@Test(expected = FormatException.class)
public void testValidateBlogPostWithoutSignature()
throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, null, body, null);
BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary();
}
@Test
public void testValidateProperBlogComment()
throws IOException, GeneralSecurityException {
// comment, parent_original_id, parent_id, signature
String comment = "This is a blog comment";
MessageId pOriginalId = new MessageId(TestUtils.getRandomId());
MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(COMMENT.getInt(), comment, pOriginalId, currentId,
sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), comment,
pOriginalId, currentId);
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(comment, result.getString(KEY_COMMENT));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals(pOriginalId.getBytes(),
result.getRaw(KEY_ORIGINAL_PARENT_MSG_ID));
assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied();
}
@Test
public void testValidateProperEmptyBlogComment()
throws IOException, GeneralSecurityException {
// comment, parent_original_id, signature, parent_current_id
MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(COMMENT.getInt(), null, originalId, currentId,
sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), null,
originalId, currentId);
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertFalse(result.containsKey(KEY_COMMENT));
context.assertIsSatisfied();
}
@Test
public void testValidateProperWrappedPost()
throws IOException, GeneralSecurityException {
// group descriptor, timestamp, content, signature
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(WRAPPED_POST.getInt(), descriptor,
message.getTimestamp(), body, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(groupFactory).createGroup(clientId, descriptor);
will(returnValue(blog.getGroup()));
oneOf(clientHelper).toByteArray(originalList);
will(returnValue(originalBody));
oneOf(messageFactory)
.createMessage(group.getId(), message.getTimestamp(),
originalBody);
will(returnValue(message));
}});
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
context.assertIsSatisfied();
}
@Test
public void testValidateProperWrappedComment()
throws IOException, GeneralSecurityException {
// group descriptor, timestamp, comment, parent_original_id, signature,
// parent_current_id
String comment = "This is another comment";
MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId oldId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
MessageId currentId = new MessageId(TestUtils.getRandomId());
BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
message.getTimestamp(), comment, originalId, oldId, sigBytes,
currentId);
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
comment, originalId, oldId);
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
originalId, oldId, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(groupFactory).createGroup(clientId, descriptor);
will(returnValue(blog.getGroup()));
oneOf(clientHelper).toByteArray(originalList);
will(returnValue(originalBody));
oneOf(messageFactory)
.createMessage(group.getId(), message.getTimestamp(),
originalBody);
will(returnValue(message));
}});
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(comment, result.getString(KEY_COMMENT));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals(
message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
context.assertIsSatisfied();
}
private void expectCrypto(final String label, final BdfList signed,
final byte[] sig) throws IOException, GeneralSecurityException {
context.checking(new Expectations() {{
oneOf(blogFactory).parseBlog(group);
will(returnValue(blog));
oneOf(clientHelper)
.verifySignature(label, sig, author.getPublicKey(), signed);
}});
}
}

View File

@@ -0,0 +1,573 @@
package org.briarproject.briar.client;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.briar.CaptureArgumentAction;
import org.briarproject.bramble.TestUtils;
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.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.briar.api.client.MessageQueueManager.IncomingQueueMessageHook;
import org.briarproject.briar.api.client.MessageQueueManager.QueueMessageValidator;
import org.briarproject.briar.api.client.QueueMessage;
import org.briarproject.briar.api.client.QueueMessageFactory;
import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
public class MessageQueueManagerImplTest extends BriarTestCase {
private final GroupId groupId = new GroupId(TestUtils.getRandomId());
private final ClientId clientId =
new ClientId(TestUtils.getRandomString(5));
private final byte[] descriptor = new byte[0];
private final Group group = new Group(groupId, clientId, descriptor);
private final long timestamp = System.currentTimeMillis();
@Test
public void testSendingMessages() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final Transaction txn = new Transaction(null, false);
final byte[] body = new byte[123];
final Metadata groupMetadata = new Metadata();
final Metadata messageMetadata = new Metadata();
final Metadata groupMetadata1 = new Metadata();
final byte[] queueState = new byte[123];
groupMetadata1.put(QUEUE_STATE_KEY, queueState);
context.checking(new Expectations() {{
// First message: queue state does not exist
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata));
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
will(new EncodeQueueStateAction(1L, 0L, new BdfList()));
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
with(any(Metadata.class)));
oneOf(queueMessageFactory).createMessage(groupId, timestamp, 0L,
body);
will(new CreateMessageAction());
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
with(messageMetadata), with(true));
// Second message: queue state exists
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata1));
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
will(new DecodeQueueStateAction(1L, 0L, new BdfList()));
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
will(new EncodeQueueStateAction(2L, 0L, new BdfList()));
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
with(any(Metadata.class)));
oneOf(queueMessageFactory).createMessage(groupId, timestamp, 1L,
body);
will(new CreateMessageAction());
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
with(messageMetadata), with(true));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// First message
QueueMessage q = mqm.sendMessage(txn, group, timestamp, body,
messageMetadata);
assertEquals(groupId, q.getGroupId());
assertEquals(timestamp, q.getTimestamp());
assertEquals(0L, q.getQueuePosition());
assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q.getLength());
// Second message
QueueMessage q1 = mqm.sendMessage(txn, group, timestamp, body,
messageMetadata);
assertEquals(groupId, q1.getGroupId());
assertEquals(timestamp, q1.getTimestamp());
assertEquals(1L, q1.getQueuePosition());
assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q1.getLength());
context.assertIsSatisfied();
}
@Test
public void testValidatorRejectsShortMessage() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<MessageValidator> captured =
new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator =
context.mock(QueueMessageValidator.class);
// The message is too short to be a valid queue message
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH - 1];
final Message message = new Message(messageId, groupId, timestamp, raw);
context.checking(new Expectations() {{
oneOf(validationManager).registerMessageValidator(with(clientId),
with(any(MessageValidator.class)));
will(new CaptureArgumentAction<MessageValidator>(captured,
MessageValidator.class, 1));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating message validator
mqm.registerMessageValidator(clientId, queueMessageValidator);
MessageValidator delegate = captured.get();
assertNotNull(delegate);
// The message should be invalid
try {
delegate.validateMessage(message, group);
fail();
} catch (InvalidMessageException expected) {
// Expected
}
context.assertIsSatisfied();
}
@Test
public void testValidatorRejectsNegativeQueuePosition() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<MessageValidator> captured =
new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator =
context.mock(QueueMessageValidator.class);
// The message has a negative queue position
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
for (int i = 0; i < 8; i++)
raw[MESSAGE_HEADER_LENGTH + i] = (byte) 0xFF;
final Message message = new Message(messageId, groupId, timestamp, raw);
context.checking(new Expectations() {{
oneOf(validationManager).registerMessageValidator(with(clientId),
with(any(MessageValidator.class)));
will(new CaptureArgumentAction<MessageValidator>(captured,
MessageValidator.class, 1));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating message validator
mqm.registerMessageValidator(clientId, queueMessageValidator);
MessageValidator delegate = captured.get();
assertNotNull(delegate);
// The message should be invalid
try {
delegate.validateMessage(message, group);
fail();
} catch (InvalidMessageException expected) {
// Expected
}
context.assertIsSatisfied();
}
@Test
public void testValidatorDelegatesValidMessage() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<MessageValidator> captured =
new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator =
context.mock(QueueMessageValidator.class);
final Metadata metadata = new Metadata();
final MessageContext messageContext =
new MessageContext(metadata);
// The message is valid, with a queue position of zero
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
final Message message = new Message(messageId, groupId, timestamp, raw);
context.checking(new Expectations() {{
oneOf(validationManager).registerMessageValidator(with(clientId),
with(any(MessageValidator.class)));
will(new CaptureArgumentAction<MessageValidator>(captured,
MessageValidator.class, 1));
// The message should be delegated
oneOf(queueMessageValidator).validateMessage(
with(any(QueueMessage.class)), with(group));
will(returnValue(messageContext));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating message validator
mqm.registerMessageValidator(clientId, queueMessageValidator);
MessageValidator delegate = captured.get();
assertNotNull(delegate);
// The message should be valid and the metadata should be returned
assertSame(messageContext, delegate.validateMessage(message, group));
assertSame(metadata, messageContext.getMetadata());
context.assertIsSatisfied();
}
@Test
public void testIncomingMessageHookDeletesDuplicateMessage()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<IncomingMessageHook> captured =
new AtomicReference<IncomingMessageHook>();
final IncomingQueueMessageHook incomingQueueMessageHook =
context.mock(IncomingQueueMessageHook.class);
final Transaction txn = new Transaction(null, false);
final Metadata groupMetadata = new Metadata();
final byte[] queueState = new byte[123];
groupMetadata.put(QUEUE_STATE_KEY, queueState);
// The message has queue position 0
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
final Message message = new Message(messageId, groupId, timestamp, raw);
context.checking(new Expectations() {{
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
with(any(IncomingMessageHook.class)));
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
IncomingMessageHook.class, 1));
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata));
// Queue position 1 is expected
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
will(new DecodeQueueStateAction(0L, 1L, new BdfList()));
// The message and its metadata should be deleted
oneOf(db).deleteMessage(txn, messageId);
oneOf(db).deleteMessageMetadata(txn, messageId);
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating incoming message hook
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
IncomingMessageHook delegate = captured.get();
assertNotNull(delegate);
// Pass the message to the hook
delegate.incomingMessage(txn, message, new Metadata());
context.assertIsSatisfied();
}
@Test
public void testIncomingMessageHookAddsOutOfOrderMessageToPendingList()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<IncomingMessageHook> captured =
new AtomicReference<IncomingMessageHook>();
final IncomingQueueMessageHook incomingQueueMessageHook =
context.mock(IncomingQueueMessageHook.class);
final Transaction txn = new Transaction(null, false);
final Metadata groupMetadata = new Metadata();
final byte[] queueState = new byte[123];
groupMetadata.put(QUEUE_STATE_KEY, queueState);
// The message has queue position 1
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
ByteUtils.writeUint64(1L, raw, MESSAGE_HEADER_LENGTH);
final Message message = new Message(messageId, groupId, timestamp, raw);
final BdfList pending = BdfList.of(BdfList.of(1L, messageId));
context.checking(new Expectations() {{
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
with(any(IncomingMessageHook.class)));
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
IncomingMessageHook.class, 1));
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata));
// Queue position 0 is expected
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
will(new DecodeQueueStateAction(0L, 0L, new BdfList()));
// The message should be added to the pending list
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
will(new EncodeQueueStateAction(0L, 0L, pending));
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
with(any(Metadata.class)));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating incoming message hook
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
IncomingMessageHook delegate = captured.get();
assertNotNull(delegate);
// Pass the message to the hook
delegate.incomingMessage(txn, message, new Metadata());
context.assertIsSatisfied();
}
@Test
public void testIncomingMessageHookDelegatesInOrderMessage()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<IncomingMessageHook> captured =
new AtomicReference<IncomingMessageHook>();
final IncomingQueueMessageHook incomingQueueMessageHook =
context.mock(IncomingQueueMessageHook.class);
final Transaction txn = new Transaction(null, false);
final Metadata groupMetadata = new Metadata();
final byte[] queueState = new byte[123];
groupMetadata.put(QUEUE_STATE_KEY, queueState);
// The message has queue position 0
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
final Message message = new Message(messageId, groupId, timestamp, raw);
final Metadata messageMetadata = new Metadata();
context.checking(new Expectations() {{
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
with(any(IncomingMessageHook.class)));
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
IncomingMessageHook.class, 1));
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata));
// Queue position 0 is expected
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
will(new DecodeQueueStateAction(0L, 0L, new BdfList()));
// Queue position 1 should be expected next
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
will(new EncodeQueueStateAction(0L, 1L, new BdfList()));
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
with(any(Metadata.class)));
// The message should be delegated
oneOf(incomingQueueMessageHook).incomingMessage(with(txn),
with(any(QueueMessage.class)), with(messageMetadata));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating incoming message hook
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
IncomingMessageHook delegate = captured.get();
assertNotNull(delegate);
// Pass the message to the hook
delegate.incomingMessage(txn, message, messageMetadata);
context.assertIsSatisfied();
}
@Test
public void testIncomingMessageHookRetrievesPendingMessage()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ClientHelper clientHelper = context.mock(ClientHelper.class);
final QueueMessageFactory queueMessageFactory =
context.mock(QueueMessageFactory.class);
final ValidationManager validationManager =
context.mock(ValidationManager.class);
final AtomicReference<IncomingMessageHook> captured =
new AtomicReference<IncomingMessageHook>();
final IncomingQueueMessageHook incomingQueueMessageHook =
context.mock(IncomingQueueMessageHook.class);
final Transaction txn = new Transaction(null, false);
final Metadata groupMetadata = new Metadata();
final byte[] queueState = new byte[123];
groupMetadata.put(QUEUE_STATE_KEY, queueState);
// The message has queue position 0
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
final Message message = new Message(messageId, groupId, timestamp, raw);
final Metadata messageMetadata = new Metadata();
// Queue position 1 is pending
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
final byte[] raw1 = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
final QueueMessage message1 = new QueueMessage(messageId1, groupId,
timestamp, 1L, raw1);
final Metadata messageMetadata1 = new Metadata();
final BdfList pending = BdfList.of(BdfList.of(1L, messageId1));
context.checking(new Expectations() {{
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
with(any(IncomingMessageHook.class)));
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
IncomingMessageHook.class, 1));
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata));
// Queue position 0 is expected, position 1 is pending
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
will(new DecodeQueueStateAction(0L, 0L, pending));
// Queue position 2 should be expected next
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
will(new EncodeQueueStateAction(0L, 2L, new BdfList()));
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
with(any(Metadata.class)));
// The new message should be delegated
oneOf(incomingQueueMessageHook).incomingMessage(with(txn),
with(any(QueueMessage.class)), with(messageMetadata));
// The pending message should be retrieved
oneOf(db).getRawMessage(txn, messageId1);
will(returnValue(raw1));
oneOf(db).getMessageMetadata(txn, messageId1);
will(returnValue(messageMetadata1));
oneOf(queueMessageFactory).createMessage(messageId1, raw1);
will(returnValue(message1));
// The pending message should be delegated
oneOf(incomingQueueMessageHook).incomingMessage(txn, message1,
messageMetadata1);
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
clientHelper, queueMessageFactory, validationManager);
// Capture the delegating incoming message hook
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
IncomingMessageHook delegate = captured.get();
assertNotNull(delegate);
// Pass the message to the hook
delegate.incomingMessage(txn, message, messageMetadata);
context.assertIsSatisfied();
}
private class EncodeQueueStateAction implements Action {
private final long outgoingPosition, incomingPosition;
private final BdfList pending;
private EncodeQueueStateAction(long outgoingPosition,
long incomingPosition, BdfList pending) {
this.outgoingPosition = outgoingPosition;
this.incomingPosition = incomingPosition;
this.pending = pending;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
BdfDictionary d = (BdfDictionary) invocation.getParameter(0);
assertEquals(outgoingPosition, d.getLong("nextOut").longValue());
assertEquals(incomingPosition, d.getLong("nextIn").longValue());
assertEquals(pending, d.getList("pending"));
return new byte[123];
}
@Override
public void describeTo(Description description) {
description.appendText("encodes a queue state");
}
}
private class DecodeQueueStateAction implements Action {
private final long outgoingPosition, incomingPosition;
private final BdfList pending;
private DecodeQueueStateAction(long outgoingPosition,
long incomingPosition, BdfList pending) {
this.outgoingPosition = outgoingPosition;
this.incomingPosition = incomingPosition;
this.pending = pending;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
BdfDictionary d = new BdfDictionary();
d.put("nextOut", outgoingPosition);
d.put("nextIn", incomingPosition);
d.put("pending", pending);
return d;
}
@Override
public void describeTo(Description description) {
description.appendText("decodes a queue state");
}
}
private class CreateMessageAction implements Action {
@Override
public Object invoke(Invocation invocation) throws Throwable {
GroupId groupId = (GroupId) invocation.getParameter(0);
long timestamp = (Long) invocation.getParameter(1);
long queuePosition = (Long) invocation.getParameter(2);
byte[] body = (byte[]) invocation.getParameter(3);
byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH + body.length];
MessageId id = new MessageId(TestUtils.getRandomId());
return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
}
@Override
public void describeTo(Description description) {
description.appendText("creates a message");
}
}
}

View File

@@ -0,0 +1,104 @@
package org.briarproject.briar.client;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.MessageTree;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import javax.annotation.Nullable;
import static org.junit.Assert.assertEquals;
public class MessageTreeImplTest {
private MessageTree<TestNode> tree;
@Test
public void testMessageTree() {
tree = new MessageTreeImpl<TestNode>();
testSimpleTree();
tree.clear();
testSimpleTree();
}
private void testSimpleTree() {
TestNode[] nodes = new TestNode[5];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = new TestNode();
}
/*
Construct the following tree:
4
1 ->
0 ->
2
3
*/
nodes[0].setParentId(nodes[1].getId());
nodes[2].setParentId(nodes[0].getId());
nodes[3].setParentId(nodes[1].getId());
long timestamp = System.currentTimeMillis();
nodes[4].setTimestamp(timestamp - 5);
nodes[1].setTimestamp(timestamp - 4);
nodes[0].setTimestamp(timestamp - 3);
nodes[3].setTimestamp(timestamp - 2);
nodes[2].setTimestamp(timestamp - 1);
// add all nodes except the last one
tree.add(Arrays.asList(Arrays.copyOf(nodes, nodes.length - 1)));
tree.add(Collections.singletonList(nodes[nodes.length - 1]));
TestNode[] sortedNodes =
tree.depthFirstOrder().toArray(new TestNode[5]);
assertEquals(nodes[4], sortedNodes[0]);
assertEquals(nodes[1], sortedNodes[1]);
assertEquals(nodes[0], sortedNodes[2]);
assertEquals(nodes[2], sortedNodes[3]);
assertEquals(nodes[3], sortedNodes[4]);
}
@NotNullByDefault
private class TestNode implements MessageTree.MessageNode {
private final MessageId id = new MessageId(TestUtils.getRandomId());
@Nullable
private MessageId parentId;
private long timestamp;
@Override
public MessageId getId() {
return id;
}
@Override
@Nullable
public MessageId getParentId() {
return parentId;
}
@Override
public void setLevel(int level) {
}
@Override
public void setDescendantCount(int descendantCount) {
}
@Override
public long getTimestamp() {
return timestamp;
}
private void setParentId(MessageId parentId) {
this.parentId = parentId;
}
private void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.forum;
import org.briarproject.briar.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ForumManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.forum;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.BriarIntegrationTest;
import org.briarproject.briar.BriarIntegrationTestComponent;
@@ -17,7 +17,7 @@ import java.util.Collection;
import javax.annotation.Nullable;
import static org.briarproject.TestUtils.assertGroupCount;
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;

View File

@@ -0,0 +1,391 @@
package org.briarproject.briar.forum;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.ValidatorTestCase;
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.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.MessageId;
import org.jmock.Expectations;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.util.Collection;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
import static org.briarproject.briar.api.forum.ForumPostFactory.SIGNING_LABEL_POST;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class ForumPostValidatorTest extends ValidatorTestCase {
private final MessageId parentId = new MessageId(TestUtils.getRandomId());
private final String authorName =
TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final byte[] authorPublicKey =
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
private final BdfList authorList = BdfList.of(authorName, authorPublicKey);
private final String content =
TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
private final byte[] signature =
TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
private final AuthorId authorId = new AuthorId(TestUtils.getRandomId());
private final Author author =
new Author(authorId, authorName, authorPublicKey);
private final BdfList signedWithParent = BdfList.of(groupId, timestamp,
parentId.getBytes(), authorList, content);
private final BdfList signedWithoutParent = BdfList.of(groupId, timestamp,
null, authorList, content);
@Test(expected = FormatException.class)
public void testRejectsTooShortBody() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBody() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, signature, 123));
}
@Test
public void testAcceptsNullParentId() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
authorPublicKey, signedWithoutParent);
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(null, authorList, content, signature));
assertExpectedContext(messageContext, false, authorName);
}
@Test(expected = FormatException.class)
public void testRejectsNonRawParentId() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(123, authorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortParentId() throws Exception {
byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(invalidParentId, authorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongParentId() throws Exception {
byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(invalidParentId, authorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNullAuthorList() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, null, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNonListAuthorList() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, 123, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortAuthorList() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, new BdfList(), content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongAuthorList() throws Exception {
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, BdfList.of(1, 2, 3), content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNullAuthorName() throws Exception {
BdfList invalidAuthorList = BdfList.of(null, authorPublicKey);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringAuthorName() throws Exception {
BdfList invalidAuthorList = BdfList.of(123, authorPublicKey);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortAuthorName() throws Exception {
BdfList invalidAuthorList = BdfList.of("", authorPublicKey);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test
public void testAcceptsMinLengthAuthorName() throws Exception {
final String shortAuthorName = TestUtils.getRandomString(1);
BdfList shortNameAuthorList =
BdfList.of(shortAuthorName, authorPublicKey);
final Author shortNameAuthor =
new Author(authorId, shortAuthorName, authorPublicKey);
final BdfList signedWithShortNameAuthor = BdfList.of(groupId, timestamp,
parentId.getBytes(), shortNameAuthorList, content);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(shortAuthorName, authorPublicKey);
will(returnValue(shortNameAuthor));
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
authorPublicKey, signedWithShortNameAuthor);
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(parentId, shortNameAuthorList, content, signature));
assertExpectedContext(messageContext, true, shortAuthorName);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongAuthorName() throws Exception {
String invalidAuthorName =
TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH + 1);
BdfList invalidAuthorList =
BdfList.of(invalidAuthorName, authorPublicKey);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNullAuthorPublicKey() throws Exception {
BdfList invalidAuthorList = BdfList.of(authorName, null);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNonRawAuthorPublicKey() throws Exception {
BdfList invalidAuthorList = BdfList.of(authorName, 123);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongAuthorPublicKey() throws Exception {
byte[] invalidAuthorPublicKey =
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
BdfList invalidAuthorList =
BdfList.of(authorName, invalidAuthorPublicKey);
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, invalidAuthorList, content, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNullContent() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, null, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringContent() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, 123, signature));
}
@Test
public void testAcceptsMinLengthContent() throws Exception {
String shortContent = "";
final BdfList signedWithShortContent = BdfList.of(groupId, timestamp,
parentId.getBytes(), authorList, shortContent);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
authorPublicKey, signedWithShortContent);
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(parentId, authorList, shortContent, signature));
assertExpectedContext(messageContext, true, authorName);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongContent() throws Exception {
String invalidContent =
TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH + 1);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, invalidContent, signature));
}
@Test(expected = FormatException.class)
public void testRejectsNullSignature() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, null));
}
@Test(expected = FormatException.class)
public void testRejectsNonRawSignature() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, 123));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSignature() throws Exception {
byte[] invalidSignature =
TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH + 1);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, invalidSignature));
}
@Test(expected = FormatException.class)
public void testRejectsIfVerifyingSignatureThrowsFormatException()
throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
authorPublicKey, signedWithParent);
will(throwException(new FormatException()));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, signature));
}
@Test(expected = InvalidMessageException.class)
public void testRejectsIfVerifyingSignatureThrowsGeneralSecurityException()
throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
will(returnValue(author));
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
authorPublicKey, signedWithParent);
will(throwException(new GeneralSecurityException()));
}});
ForumPostValidator v = new ForumPostValidator(authorFactory,
clientHelper, metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(parentId, authorList, content, signature));
}
private void assertExpectedContext(BdfMessageContext messageContext,
boolean hasParent, String authorName) throws FormatException {
BdfDictionary meta = messageContext.getDictionary();
Collection<MessageId> dependencies = messageContext.getDependencies();
if (hasParent) {
assertEquals(4, meta.size());
assertArrayEquals(parentId.getBytes(), meta.getRaw("parent"));
assertEquals(1, dependencies.size());
assertEquals(parentId, dependencies.iterator().next());
} else {
assertEquals(3, meta.size());
assertEquals(0, dependencies.size());
}
assertEquals(timestamp, meta.getLong("timestamp").longValue());
assertFalse(meta.getBoolean("read"));
BdfDictionary authorMeta = meta.getDictionary("author");
assertEquals(3, authorMeta.size());
assertArrayEquals(authorId.getBytes(), authorMeta.getRaw("id"));
assertEquals(authorName, authorMeta.getString("name"));
assertArrayEquals(authorPublicKey, authorMeta.getRaw("publicKey"));
}
}

View File

@@ -0,0 +1,427 @@
package org.briarproject.briar.introduction;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.ClientId;
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 org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.introduction.IntroduceeManager.SIGNING_LABEL_RESPONSE;
import static org.hamcrest.Matchers.array;
import static org.hamcrest.Matchers.samePropertyValuesAs;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class IntroduceeManagerTest extends BriarTestCase {
private final Mockery context;
private final IntroduceeManager introduceeManager;
private final DatabaseComponent db;
private final CryptoComponent cryptoComponent;
private final ClientHelper clientHelper;
private final IntroductionGroupFactory introductionGroupFactory;
private final AuthorFactory authorFactory;
private final ContactManager contactManager;
private final Clock clock;
private final Contact introducer;
private final Contact introducee1;
private final Contact introducee2;
private final Group localGroup1;
private final Group introductionGroup1;
private final Transaction txn;
private final long time = 42L;
private final Message localStateMessage;
private final SessionId sessionId;
private final Message message1;
public IntroduceeManagerTest() {
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
MessageSender messageSender = context.mock(MessageSender.class);
db = context.mock(DatabaseComponent.class);
cryptoComponent = context.mock(CryptoComponent.class);
clientHelper = context.mock(ClientHelper.class);
clock = context.mock(Clock.class);
introductionGroupFactory =
context.mock(IntroductionGroupFactory.class);
TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class);
authorFactory = context.mock(AuthorFactory.class);
contactManager = context.mock(ContactManager.class);
IdentityManager identityManager = context.mock(IdentityManager.class);
introduceeManager = new IntroduceeManager(messageSender, db,
clientHelper, clock, cryptoComponent, transportPropertyManager,
authorFactory, contactManager, identityManager,
introductionGroupFactory);
AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
Author author0 = new Author(authorId0, "Introducer",
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
AuthorId localAuthorId = new AuthorId(TestUtils.getRandomId());
ContactId contactId0 = new ContactId(234);
introducer =
new Contact(contactId0, author0, localAuthorId, true, true);
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
Author author1 = new Author(authorId1, "Introducee1",
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
Author author2 = new Author(authorId2, "Introducee2",
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId, true, true);
ClientId clientId = IntroductionManagerImpl.CLIENT_ID;
localGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
sessionId = new SessionId(TestUtils.getRandomId());
localStateMessage = new Message(
new MessageId(TestUtils.getRandomId()),
localGroup1.getId(),
time,
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
message1 = new Message(
new MessageId(TestUtils.getRandomId()),
introductionGroup1.getId(),
time,
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
txn = new Transaction(null, false);
}
@Test
public void testIncomingRequestMessage()
throws DbException, FormatException {
final BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
final BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
context.checking(new Expectations() {{
oneOf(clientHelper).mergeMessageMetadata(txn,
localStateMessage.getId(), state);
}});
introduceeManager.incomingMessage(txn, state, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testIncomingResponseMessage()
throws DbException, FormatException {
final BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
final BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal());
// turn request message into a response
msg.put(ACCEPT, true);
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
msg.put(TRANSPORT, new BdfDictionary());
context.checking(new Expectations() {{
oneOf(clientHelper).mergeMessageMetadata(txn,
localStateMessage.getId(), state);
}});
introduceeManager.incomingMessage(txn, state, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testDetectReplacedEphemeralPublicKey()
throws DbException, FormatException, GeneralSecurityException {
// TODO MR !237 should use its new default initialization method here
final BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
final BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
// prepare state for incoming ACK
state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal());
state.put(ADDED_CONTACT_ID, 2);
final byte[] nonce = TestUtils.getRandomBytes(42);
state.put(NONCE, nonce);
state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
// create incoming ACK message
final byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
final byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
BdfDictionary ack = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(GROUP_ID, introductionGroup1.getId()),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce,
introducee2.getAuthor().getPublicKey(), sig);
will(returnValue(false));
}});
try {
introduceeManager.incomingMessage(txn, state, ack);
fail();
} catch (DbException e) {
// expected
assertTrue(e.getCause() instanceof GeneralSecurityException);
}
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testSignatureVerification()
throws FormatException, DbException, GeneralSecurityException {
final byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
final byte[] nonce = TestUtils.getRandomBytes(MAC_LENGTH);
final byte[] sig = TestUtils.getRandomBytes(MAC_LENGTH);
BdfDictionary state = new BdfDictionary();
state.put(PUBLIC_KEY, publicKeyBytes);
state.put(NONCE, nonce);
state.put(SIGNATURE, sig);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce,
publicKeyBytes, sig);
will(returnValue(true));
}});
introduceeManager.verifySignature(state);
context.assertIsSatisfied();
}
@Test
public void testMacVerification()
throws FormatException, DbException, GeneralSecurityException {
final byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
final BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake"));
final byte[] ePublicKeyBytes =
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
final SecretKey macKey = TestUtils.getSecretKey();
// move state to where it would be after an ACK arrived
BdfDictionary state = new BdfDictionary();
state.put(PUBLIC_KEY, publicKeyBytes);
state.put(TRANSPORT, tp);
state.put(TIME, time);
state.put(E_PUBLIC_KEY, ePublicKeyBytes);
state.put(MAC, mac);
state.put(MAC_KEY, macKey.getBytes());
final byte[] signBytes = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
will(returnValue(signBytes));
//noinspection unchecked
oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)),
with(array(equal(signBytes))));
will(returnValue(mac));
}});
introduceeManager.verifyMac(state);
context.assertIsSatisfied();
// now produce wrong MAC
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
will(returnValue(signBytes));
//noinspection unchecked
oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)),
with(array(equal(signBytes))));
will(returnValue(TestUtils.getRandomBytes(MAC_LENGTH)));
}});
try {
introduceeManager.verifyMac(state);
fail();
} catch(GeneralSecurityException e) {
// expected
}
context.assertIsSatisfied();
}
private BdfDictionary initializeSessionState(final Transaction txn,
final GroupId groupId, final BdfDictionary msg)
throws DbException, FormatException {
final SecureRandom secureRandom = context.mock(SecureRandom.class);
final Bytes salt = new Bytes(new byte[64]);
final BdfDictionary groupMetadata = BdfDictionary.of(
new BdfEntry(CONTACT, introducee1.getId().getInt())
);
final boolean contactExists = false;
final BdfDictionary state = new BdfDictionary();
state.put(STORAGE_ID, localStateMessage.getId());
state.put(STATE, AWAIT_REQUEST.getValue());
state.put(ROLE, ROLE_INTRODUCEE);
state.put(GROUP_ID, groupId);
state.put(INTRODUCER, introducer.getAuthor().getName());
state.put(CONTACT_ID_1, introducer.getId().getInt());
state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
state.put(NOT_OUR_RESPONSE, localStateMessage.getId());
state.put(ANSWERED, false);
state.put(EXISTS, contactExists);
state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId());
state.put(REMOTE_AUTHOR_IS_US, false);
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(cryptoComponent).getSecureRandom();
will(returnValue(secureRandom));
oneOf(secureRandom).nextBytes(salt.getBytes());
oneOf(introductionGroupFactory).createLocalGroup();
will(returnValue(localGroup1));
oneOf(clientHelper)
.createMessage(localGroup1.getId(), time, BdfList.of(salt));
will(returnValue(localStateMessage));
// who is making the introduction? who is the introducer?
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
groupId);
will(returnValue(groupMetadata));
oneOf(db).getContact(txn, introducer.getId());
will(returnValue(introducer));
// create remote author to check if contact exists
oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(),
introducee2.getAuthor().getPublicKey());
will(returnValue(introducee2.getAuthor()));
oneOf(contactManager)
.contactExists(txn, introducee2.getAuthor().getId(),
introducer.getLocalAuthorId());
will(returnValue(contactExists));
// checks if remote author is one of our identities
oneOf(db).containsLocalAuthor(txn, introducee2.getAuthor().getId());
will(returnValue(false));
// store session state
oneOf(clientHelper)
.addLocalMessage(txn, localStateMessage, state, false);
}});
BdfDictionary result = introduceeManager.initialize(txn, groupId, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
return result;
}
}

View File

@@ -0,0 +1,189 @@
package org.briarproject.briar.introduction;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.ClientId;
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 org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.security.SecureRandom;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.junit.Assert.assertFalse;
public class IntroducerManagerTest extends BriarTestCase {
private final Mockery context;
private final IntroducerManager introducerManager;
private final CryptoComponent cryptoComponent;
private final ClientHelper clientHelper;
private final IntroductionGroupFactory introductionGroupFactory;
private final MessageSender messageSender;
private final Clock clock;
private final Contact introducee1;
private final Contact introducee2;
private final Group localGroup0;
private final Group introductionGroup1;
private final Group introductionGroup2;
public IntroducerManagerTest() {
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
messageSender = context.mock(MessageSender.class);
cryptoComponent = context.mock(CryptoComponent.class);
clientHelper = context.mock(ClientHelper.class);
clock = context.mock(Clock.class);
introductionGroupFactory =
context.mock(IntroductionGroupFactory.class);
introducerManager =
new IntroducerManager(messageSender, clientHelper, clock,
cryptoComponent, introductionGroupFactory);
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
Author author1 = new Author(authorId1, "Introducee1",
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
Author author2 = new Author(authorId2, "Introducee2",
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId2, true, true);
localGroup0 = new Group(new GroupId(TestUtils.getRandomId()),
getClientId(), new byte[0]);
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
getClientId(), new byte[0]);
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
getClientId(), new byte[0]);
context.assertIsSatisfied();
}
@Test
public void testMakeIntroduction() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
final long time = 42L;
context.setImposteriser(ClassImposteriser.INSTANCE);
final SecureRandom secureRandom = context.mock(SecureRandom.class);
final Bytes salt = new Bytes(new byte[64]);
final Message msg = new Message(new MessageId(TestUtils.getRandomId()),
localGroup0.getId(), time, TestUtils.getRandomBytes(64));
final BdfDictionary state = new BdfDictionary();
state.put(SESSION_ID, msg.getId());
state.put(STORAGE_ID, msg.getId());
state.put(STATE, PREPARE_REQUESTS.getValue());
state.put(ROLE, ROLE_INTRODUCER);
state.put(GROUP_ID_1, introductionGroup1.getId());
state.put(GROUP_ID_2, introductionGroup2.getId());
state.put(CONTACT_1, introducee1.getAuthor().getName());
state.put(CONTACT_2, introducee2.getAuthor().getName());
state.put(CONTACT_ID_1, introducee1.getId().getInt());
state.put(CONTACT_ID_2, introducee2.getId().getInt());
state.put(AUTHOR_ID_1, introducee1.getAuthor().getId());
state.put(AUTHOR_ID_2, introducee2.getAuthor().getId());
final BdfDictionary state2 = (BdfDictionary) state.clone();
state2.put(STATE, AWAIT_RESPONSES.getValue());
final BdfDictionary msg1 = new BdfDictionary();
msg1.put(TYPE, TYPE_REQUEST);
msg1.put(SESSION_ID, state.getRaw(SESSION_ID));
msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1));
msg1.put(NAME, state.getString(CONTACT_2));
msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
final BdfDictionary msg1send = (BdfDictionary) msg1.clone();
msg1send.put(MESSAGE_TIME, time);
final BdfDictionary msg2 = new BdfDictionary();
msg2.put(TYPE, TYPE_REQUEST);
msg2.put(SESSION_ID, state.getRaw(SESSION_ID));
msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2));
msg2.put(NAME, state.getString(CONTACT_1));
msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey());
final BdfDictionary msg2send = (BdfDictionary) msg2.clone();
msg2send.put(MESSAGE_TIME, time);
context.checking(new Expectations() {{
// initialize and store session state
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(cryptoComponent).getSecureRandom();
will(returnValue(secureRandom));
oneOf(secureRandom).nextBytes(salt.getBytes());
oneOf(introductionGroupFactory).createLocalGroup();
will(returnValue(localGroup0));
oneOf(clientHelper).createMessage(localGroup0.getId(), time,
BdfList.of(salt));
will(returnValue(msg));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2));
oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
// send message
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);
oneOf(messageSender).sendMessage(txn, msg1send);
oneOf(messageSender).sendMessage(txn, msg2send);
}});
introducerManager
.makeIntroduction(txn, introducee1, introducee2, null, time);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
private ClientId getClientId() {
return IntroductionManagerImpl.CLIENT_ID;
}
}

View File

@@ -2,8 +2,8 @@ package org.briarproject.briar.introduction;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestUtils;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
@@ -51,8 +51,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.TestUtils.assertGroupCount;
import static org.briarproject.briar.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;

View File

@@ -1,8 +1,8 @@
package org.briarproject.briar.introduction;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestPluginConfigModule;
import org.briarproject.TestSeedProviderModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.briar.TestPluginConfigModule;
import org.briarproject.bramble.TestSeedProviderModule;
import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoModule;

View File

@@ -0,0 +1,297 @@
package org.briarproject.briar.introduction;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.ClientId;
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.sync.MessageStatus;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.junit.Assert.assertFalse;
public class IntroductionManagerImplTest extends BriarTestCase {
private final Mockery context;
private final IntroductionManagerImpl introductionManager;
private final IntroducerManager introducerManager;
private final IntroduceeManager introduceeManager;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final MessageTracker messageTracker;
private final IntroductionGroupFactory introductionGroupFactory;
private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
private final MessageId storageId = new MessageId(sessionId.getBytes());
private final long time = 42L;
private final Contact introducee1;
private final Contact introducee2;
private final Group introductionGroup1;
private final Group introductionGroup2;
private final Message message1;
private Transaction txn;
public IntroductionManagerImplTest() {
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
Author author1 = new Author(authorId1, "Introducee1",
new byte[MAX_PUBLIC_KEY_LENGTH]);
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
Author author2 = new Author(authorId2, "Introducee2",
new byte[MAX_PUBLIC_KEY_LENGTH]);
AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId2, true, true);
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
message1 = new Message(
new MessageId(TestUtils.getRandomId()),
introductionGroup1.getId(),
time,
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
// mock ALL THE THINGS!!!
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
introducerManager = context.mock(IntroducerManager.class);
introduceeManager = context.mock(IntroduceeManager.class);
db = context.mock(DatabaseComponent.class);
clientHelper = context.mock(ClientHelper.class);
MetadataParser metadataParser = context.mock(MetadataParser.class);
messageTracker = context.mock(MessageTracker.class);
introductionGroupFactory = context.mock(IntroductionGroupFactory.class);
introductionManager = new IntroductionManagerImpl(db, clientHelper,
metadataParser, messageTracker, introducerManager,
introduceeManager, introductionGroupFactory);
}
@Test
public void testMakeIntroduction() throws DbException, FormatException {
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(introducerManager)
.makeIntroduction(txn, introducee1, introducee2, null,
time);
// get both introduction groups
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2));
// track message for group 1
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
// track message for group 2
oneOf(messageTracker).trackMessage(txn,
introductionGroup2.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.makeIntroduction(introducee1, introducee2, null, time);
context.assertIsSatisfied();
}
@Test
public void testAcceptIntroduction() throws DbException, FormatException {
final BdfDictionary state = BdfDictionary.of(
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
);
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introduceeManager).acceptIntroduction(txn, state, time);
// track message
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.acceptIntroduction(introducee1.getId(), sessionId, time);
context.assertIsSatisfied();
}
@Test
public void testDeclineIntroduction() throws DbException, FormatException {
final BdfDictionary state = BdfDictionary.of(
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
);
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introduceeManager).declineIntroduction(txn, state, time);
// track message
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.declineIntroduction(introducee1.getId(), sessionId, time);
context.assertIsSatisfied();
}
@Test
public void testGetIntroductionMessages()
throws DbException, FormatException {
final Map<MessageId, BdfDictionary> metadata = Collections.emptyMap();
final Collection<MessageStatus> statuses = Collections.emptyList();
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
introductionGroup1.getId());
will(returnValue(metadata));
oneOf(db).getMessageStatus(txn, introducee1.getId(),
introductionGroup1.getId());
will(returnValue(statuses));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager.getIntroductionMessages(introducee1.getId());
context.assertIsSatisfied();
}
@Test
public void testIncomingRequestMessage()
throws DbException, FormatException {
final BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
final BdfDictionary state = new BdfDictionary();
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(introduceeManager)
.initialize(txn, introductionGroup1.getId(), msg);
will(returnValue(state));
oneOf(introduceeManager)
.incomingMessage(txn, state, msg);
// track message
oneOf(messageTracker).trackIncomingMessage(txn, message1);
}});
introductionManager
.incomingMessage(txn, message1, new BdfList(), msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testIncomingResponseMessage()
throws DbException, FormatException {
final BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_RESPONSE),
new BdfEntry(SESSION_ID, sessionId)
);
final BdfDictionary state = new BdfDictionary();
state.put(ROLE, ROLE_INTRODUCER);
state.put(GROUP_ID_1, introductionGroup1.getId());
state.put(GROUP_ID_2, introductionGroup2.getId());
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introducerManager).incomingMessage(txn, state, msg);
// track message
oneOf(messageTracker).trackIncomingMessage(txn, message1);
}});
introductionManager
.incomingMessage(txn, message1, new BdfList(), msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
}

View File

@@ -0,0 +1,368 @@
package org.briarproject.briar.introduction;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
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.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.ClientId;
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 org.briarproject.bramble.system.SystemClock;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.IOException;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class IntroductionValidatorTest extends BriarTestCase {
private final Mockery context = new Mockery();
private final Group group;
private final Message message;
private final IntroductionValidator validator;
private final Clock clock = new SystemClock();
public IntroductionValidatorTest() {
GroupId groupId = new GroupId(TestUtils.getRandomId());
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
byte[] descriptor = TestUtils.getRandomBytes(12);
group = new Group(groupId, clientId, descriptor);
MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
byte[] raw = TestUtils.getRandomBytes(123);
message = new Message(messageId, group.getId(), timestamp, raw);
ClientHelper clientHelper = context.mock(ClientHelper.class);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
validator = new IntroductionValidator(clientHelper, metadataEncoder,
clock);
context.assertIsSatisfied();
}
//
// Introduction Requests
//
@Test
public void testValidateProperIntroductionRequest() throws IOException {
final byte[] sessionId = TestUtils.getRandomId();
final String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
final byte[] publicKey =
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final String text =
TestUtils.getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
name, publicKey, text);
final BdfDictionary result =
validator.validateMessage(message, group, body)
.getDictionary();
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
assertEquals(name, result.getString(NAME));
assertEquals(publicKey, result.getRaw(PUBLIC_KEY));
assertEquals(text, result.getString(MSG));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithNoName() throws IOException {
BdfDictionary msg = getValidIntroductionRequest();
// no NAME is message
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithLongName()
throws IOException {
// too long NAME in message
BdfDictionary msg = getValidIntroductionRequest();
msg.put(NAME, msg.get(NAME) + "x");
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithWrongType()
throws IOException {
// wrong message type
BdfDictionary msg = getValidIntroductionRequest();
msg.put(TYPE, 324234);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
private BdfDictionary getValidIntroductionRequest() throws FormatException {
byte[] sessionId = TestUtils.getRandomId();
String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
msg.put(SESSION_ID, sessionId);
msg.put(NAME, name);
msg.put(PUBLIC_KEY, publicKey);
msg.put(MSG, text);
return msg;
}
//
// Introduction Responses
//
@Test
public void testValidateIntroductionAcceptResponse() throws IOException {
byte[] groupId = TestUtils.getRandomId();
byte[] sessionId = TestUtils.getRandomId();
long time = clock.currentTimeMillis();
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
String transportId = TestUtils
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = BdfDictionary.of(
new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH),
TestUtils.getRandomString(MAX_PROPERTY_LENGTH))
);
BdfDictionary tp = BdfDictionary.of(
new BdfEntry(transportId, tProps)
);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, groupId);
msg.put(SESSION_ID, sessionId);
msg.put(ACCEPT, true);
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, publicKey);
msg.put(TRANSPORT, tp);
BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
final BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
assertEquals(true, result.getBoolean(ACCEPT));
assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY));
assertEquals(tp, result.getDictionary(TRANSPORT));
context.assertIsSatisfied();
}
@Test
public void testValidateIntroductionDeclineResponse()
throws IOException {
BdfDictionary msg = getValidIntroductionResponse(false);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT));
BdfDictionary result = validator.validateMessage(message, group, body)
.getDictionary();
assertFalse(result.getBoolean(ACCEPT));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithoutAccept()
throws IOException {
BdfDictionary msg = getValidIntroductionResponse(false);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithBrokenTp()
throws IOException {
BdfDictionary msg = getValidIntroductionResponse(true);
BdfDictionary tp = msg.getDictionary(TRANSPORT);
tp.put(TestUtils
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
msg.put(TRANSPORT, tp);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithoutPublicKey()
throws IOException {
BdfDictionary msg = getValidIntroductionResponse(true);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getDictionary(TRANSPORT));
validator.validateMessage(message, group, body);
}
private BdfDictionary getValidIntroductionResponse(boolean accept)
throws FormatException {
byte[] groupId = TestUtils.getRandomId();
byte[] sessionId = TestUtils.getRandomId();
long time = clock.currentTimeMillis();
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
String transportId = TestUtils
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = BdfDictionary.of(
new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH),
TestUtils.getRandomString(MAX_PROPERTY_LENGTH))
);
BdfDictionary tp = BdfDictionary.of(
new BdfEntry(transportId, tProps)
);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, groupId);
msg.put(SESSION_ID, sessionId);
msg.put(ACCEPT, accept);
if (accept) {
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, publicKey);
msg.put(TRANSPORT, tp);
}
return msg;
}
//
// Introduction ACK
//
@Test
public void testValidateProperIntroductionAck() throws IOException {
byte[] sessionId = TestUtils.getRandomId();
byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
assertArrayEquals(mac, result.getRaw(MAC));
assertArrayEquals(sig, result.getRaw(SIGNATURE));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateTooLongIntroductionAck() throws IOException {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
new BdfEntry("garbage", TestUtils.getRandomString(255))
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString("garbage"));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionAckWithLongSessionId()
throws IOException {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
validator.validateMessage(message, group, body);
}
//
// Introduction Abort
//
@Test
public void testValidateProperIntroductionAbort() throws IOException {
byte[] sessionId = TestUtils.getRandomId();
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_ABORT);
msg.put(SESSION_ID, sessionId);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateTooLongIntroductionAbort() throws IOException {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ABORT),
new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
new BdfEntry("garbage", TestUtils.getRandomString(255))
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString("garbage"));
validator.validateMessage(message, group, body);
}
}

View File

@@ -0,0 +1,100 @@
package org.briarproject.briar.introduction;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.bramble.TestUtils;
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.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.junit.Assert.assertFalse;
public class MessageSenderTest extends BriarTestCase {
private final Mockery context;
private final MessageSender messageSender;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final MetadataEncoder metadataEncoder;
private final MessageQueueManager messageQueueManager;
private final Clock clock;
public MessageSenderTest() {
context = new Mockery();
db = context.mock(DatabaseComponent.class);
clientHelper = context.mock(ClientHelper.class);
metadataEncoder =
context.mock(MetadataEncoder.class);
messageQueueManager =
context.mock(MessageQueueManager.class);
clock = context.mock(Clock.class);
messageSender = new MessageSender(db, clientHelper, clock,
metadataEncoder, messageQueueManager);
}
@Test
public void testSendMessage() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
final Group privateGroup =
new Group(new GroupId(TestUtils.getRandomId()),
new ClientId(TestUtils.getRandomString(5)),
new byte[0]);
final SessionId sessionId = new SessionId(TestUtils.getRandomId());
byte[] mac = TestUtils.getRandomBytes(42);
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
final long time = 42L;
final BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(GROUP_ID, privateGroup.getId()),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
final BdfList bodyList =
BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
final byte[] body = TestUtils.getRandomBytes(8);
final Metadata metadata = new Metadata();
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(bodyList);
will(returnValue(body));
oneOf(db).getGroup(txn, privateGroup.getId());
will(returnValue(privateGroup));
oneOf(metadataEncoder).encode(msg);
will(returnValue(metadata));
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(messageQueueManager)
.sendMessage(txn, privateGroup, time, body, metadata);
}});
messageSender.sendMessage(txn, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.messaging;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PrivateKey;
@@ -11,6 +10,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.briar.api.forum.ForumConstants;
import org.briarproject.briar.api.forum.ForumPost;
import org.briarproject.briar.api.forum.ForumPostFactory;
@@ -20,7 +20,7 @@ import org.junit.Test;
import javax.inject.Inject;
import static org.briarproject.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;

View File

@@ -1,8 +1,8 @@
package org.briarproject.briar.messaging;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestLifecycleModule;
import org.briarproject.TestSeedProviderModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.briar.TestLifecycleModule;
import org.briarproject.bramble.TestSeedProviderModule;
import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.data.DataModule;

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.messaging;
import org.briarproject.briar.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class MessagingManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -0,0 +1,84 @@
package org.briarproject.briar.messaging;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.ValidatorTestCase;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.junit.Test;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class PrivateMessageValidatorTest extends ValidatorTestCase {
@Test(expected = FormatException.class)
public void testRejectsTooShortBody() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, new BdfList());
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBody() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of("", 123));
}
@Test(expected = FormatException.class)
public void testRejectsNullContent() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of((String) null));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringContent() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of(123));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongContent() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
String invalidContent =
TestUtils.getRandomString(MAX_PRIVATE_MESSAGE_BODY_LENGTH + 1);
v.validateMessage(message, group, BdfList.of(invalidContent));
}
@Test
public void testAcceptsMaxLengthContent() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
String content =
TestUtils.getRandomString(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
BdfMessageContext messageContext =
v.validateMessage(message, group, BdfList.of(content));
assertExpectedContext(messageContext);
}
@Test
public void testAcceptsMinLengthContent() throws Exception {
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext =
v.validateMessage(message, group, BdfList.of(""));
assertExpectedContext(messageContext);
}
private void assertExpectedContext(BdfMessageContext messageContext)
throws FormatException {
BdfDictionary meta = messageContext.getDictionary();
assertEquals(3, meta.size());
assertEquals(timestamp, meta.getLong("timestamp").longValue());
assertFalse(meta.getBoolean("local"));
assertFalse(meta.getBoolean(MSG_KEY_READ));
assertEquals(0, messageContext.getDependencies().size());
}
}

View File

@@ -1,8 +1,7 @@
package org.briarproject.briar.messaging;
import org.briarproject.BriarTestCase;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestUtils;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
@@ -23,6 +22,7 @@ import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import org.briarproject.bramble.system.SystemModule;
import org.briarproject.briar.BriarTestCase;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -36,10 +36,10 @@ import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import static org.briarproject.TestPluginConfigModule.MAX_LATENCY;
import static org.briarproject.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.briar.TestPluginConfigModule.MAX_LATENCY;
import static org.briarproject.briar.TestPluginConfigModule.TRANSPORT_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

View File

@@ -1,8 +1,8 @@
package org.briarproject.briar.messaging;
import org.briarproject.TestDatabaseModule;
import org.briarproject.TestPluginConfigModule;
import org.briarproject.TestSeedProviderModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.briar.TestPluginConfigModule;
import org.briarproject.bramble.TestSeedProviderModule;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.privategroup;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.briar.BriarIntegrationTest;
@@ -22,7 +22,7 @@ import java.util.Collection;
import javax.annotation.Nullable;
import static org.briarproject.TestUtils.assertGroupCount;
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

View File

@@ -0,0 +1,648 @@
package org.briarproject.briar.privategroup;
import org.briarproject.bramble.ValidatorTestCase;
import org.briarproject.bramble.api.FormatException;
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.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.privategroup.MessageType;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
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.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getRandomString;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.briar.api.privategroup.GroupMessageFactory.SIGNING_LABEL_JOIN;
import static org.briarproject.briar.api.privategroup.GroupMessageFactory.SIGNING_LABEL_POST;
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
import static org.briarproject.briar.api.privategroup.MessageType.POST;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_ID;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_NAME;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_READ;
import static org.briarproject.briar.privategroup.GroupConstants.KEY_TIMESTAMP;
import static org.briarproject.briar.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);
}
}

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.privategroup;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.privategroup;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.Transaction;
@@ -21,8 +21,8 @@ import org.junit.Test;
import java.util.Collection;
import static org.briarproject.TestUtils.getRandomBytes;
import static org.briarproject.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;

View File

@@ -0,0 +1,224 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.BrambleMockTestCase;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
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 org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.jmock.Expectations;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.*;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
import static org.junit.Assert.assertEquals;
public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
protected final DatabaseComponent db =
context.mock(DatabaseComponent.class);
protected final ClientHelper clientHelper =
context.mock(ClientHelper.class);
protected final PrivateGroupFactory privateGroupFactory =
context.mock(PrivateGroupFactory.class);
protected final PrivateGroupManager privateGroupManager =
context.mock(PrivateGroupManager.class);
protected final MessageParser messageParser =
context.mock(MessageParser.class);
protected final GroupMessageFactory groupMessageFactory =
context.mock(GroupMessageFactory.class);
protected final IdentityManager identityManager =
context.mock(IdentityManager.class);
protected final MessageEncoder messageEncoder =
context.mock(MessageEncoder.class);
protected final MessageTracker messageTracker =
context.mock(MessageTracker.class);
protected final Clock clock = context.mock(Clock.class);
protected final Transaction txn = new Transaction(null, false);
protected final GroupId contactGroupId = new GroupId(getRandomId());
protected final GroupId privateGroupId = new GroupId(getRandomId());
protected final Group privateGroupGroup =
new Group(privateGroupId, CLIENT_ID, getRandomBytes(5));
private final AuthorId authorId = new AuthorId(getRandomId());
protected final Author author =
new Author(authorId, "Author", getRandomBytes(12));
protected final PrivateGroup privateGroup =
new PrivateGroup(privateGroupGroup, "Private Group", author,
getRandomBytes(8));
protected final byte[] signature = getRandomBytes(42);
protected final MessageId lastLocalMessageId = new MessageId(getRandomId());
protected final MessageId lastRemoteMessageId =
new MessageId(getRandomId());
protected final long localTimestamp = 3L;
protected final long inviteTimestamp = 6L;
protected final long messageTimestamp = inviteTimestamp + 1;
protected final MessageId messageId = new MessageId(getRandomId());
protected final Message message =
new Message(messageId, contactGroupId, messageTimestamp,
getRandomBytes(42));
private final BdfDictionary meta =
BdfDictionary.of(new BdfEntry("me", "ta"));
protected final ContactId contactId = new ContactId(5);
protected final Contact contact =
new Contact(contactId, author, new AuthorId(getRandomId()), true,
true);
protected final InviteMessage inviteMessage =
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, privateGroup.getName(),
privateGroup.getCreator(), privateGroup.getSalt(), "msg",
signature);
protected final JoinMessage joinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastRemoteMessageId);
protected final LeaveMessage leaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastRemoteMessageId);
protected final AbortMessage abortMessage =
new AbortMessage(messageId, contactGroupId, privateGroupId,
inviteTimestamp + 1);
protected void assertSessionConstantsUnchanged(Session s1, Session s2) {
assertEquals(s1.getRole(), s2.getRole());
assertEquals(s1.getContactGroupId(), s2.getContactGroupId());
assertEquals(s1.getPrivateGroupId(), s2.getPrivateGroupId());
}
protected void assertSessionRecordedSentMessage(Session s) {
assertEquals(messageId, s.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, s.getLastRemoteMessageId());
assertEquals(messageTimestamp, s.getLocalTimestamp());
assertEquals(inviteTimestamp, s.getInviteTimestamp());
}
protected void expectGetLocalTimestamp(final long time) {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
}
protected void expectSendInviteMessage(final String msg)
throws Exception {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeInviteMessage(contactGroupId, privateGroupId,
inviteTimestamp, privateGroup.getName(), author,
privateGroup.getSalt(), msg, signature);
will(returnValue(message));
}});
expectSendMessage(INVITE, true);
}
protected void expectSendJoinMessage(final JoinMessage m, boolean visible)
throws Exception {
expectGetLocalTimestamp(messageTimestamp);
context.checking(new Expectations() {{
oneOf(messageEncoder).encodeJoinMessage(m.getContactGroupId(),
m.getPrivateGroupId(), m.getTimestamp(),
lastLocalMessageId);
will(returnValue(message));
}});
expectSendMessage(JOIN, visible);
}
protected void expectSendLeaveMessage(boolean visible) throws Exception {
expectGetLocalTimestamp(messageTimestamp);
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeLeaveMessage(contactGroupId, privateGroupId,
messageTimestamp, lastLocalMessageId);
will(returnValue(message));
}});
expectSendMessage(LEAVE, visible);
}
protected void expectSendAbortMessage() throws Exception {
expectGetLocalTimestamp(messageTimestamp);
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeAbortMessage(contactGroupId, privateGroupId,
messageTimestamp);
will(returnValue(message));
}});
expectSendMessage(ABORT, false);
}
private void expectSendMessage(final MessageType type,
final boolean visible) throws Exception {
context.checking(new Expectations() {{
oneOf(messageEncoder).encodeMetadata(type, privateGroupId,
message.getTimestamp(), true, true, visible, false);
will(returnValue(meta));
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
}});
}
protected void expectSetPrivateGroupVisibility(final Group.Visibility v)
throws Exception {
expectGetContactId();
context.checking(new Expectations() {{
oneOf(db).setGroupVisibility(txn, contactId, privateGroupId, v);
}});
}
protected void expectGetContactId() throws Exception {
final BdfDictionary groupMeta = BdfDictionary
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, contactId.getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper)
.getGroupMetadataAsDictionary(txn, contactGroupId);
will(returnValue(groupMeta));
}});
}
protected void expectIsSubscribedPrivateGroup()
throws Exception {
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, privateGroupId);
will(returnValue(true));
oneOf(db).getGroup(txn, privateGroupId);
will(returnValue(privateGroupGroup));
}});
}
protected void expectIsNotSubscribedPrivateGroup()
throws Exception {
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, privateGroupId);
will(returnValue(false));
}});
}
protected void expectMarkMessageVisibleInUi(final MessageId m,
final boolean visible)
throws Exception {
final BdfDictionary d = new BdfDictionary();
context.checking(new Expectations() {{
oneOf(messageEncoder).setVisibleInUi(d, visible);
oneOf(clientHelper).mergeMessageMetadata(txn, m, d);
}});
}
}

View File

@@ -0,0 +1,471 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.ERROR;
import static org.briarproject.briar.privategroup.invitation.CreatorState.INVITED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.JOINED;
import static org.briarproject.briar.privategroup.invitation.CreatorState.LEFT;
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
import static org.junit.Assert.assertEquals;
public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
private final CreatorProtocolEngine engine =
new CreatorProtocolEngine(db, clientHelper, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock);
private CreatorSession getDefaultSession(CreatorState state) {
return new CreatorSession(contactGroupId, privateGroupId,
lastLocalMessageId, lastRemoteMessageId, localTimestamp,
inviteTimestamp, state);
}
// onInviteAction
@Test
public void testOnInviteActionFromStart() throws Exception {
CreatorSession session =
new CreatorSession(contactGroupId, privateGroupId);
String message = "Invitation Message";
expectOnLocalInvite(message);
CreatorSession newSession =
engine.onInviteAction(txn, session, message, inviteTimestamp,
signature);
assertEquals(INVITED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(null, newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnInviteActionFromStartWithNullMessage() throws Exception {
CreatorSession session =
new CreatorSession(contactGroupId, privateGroupId);
expectOnLocalInvite(null);
CreatorSession newSession =
engine.onInviteAction(txn, session, null, inviteTimestamp,
signature);
assertEquals(INVITED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(null, newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
private void expectOnLocalInvite(final String msg) throws Exception {
context.checking(new Expectations() {{
oneOf(db).getGroup(txn, privateGroupId);
will(returnValue(privateGroupGroup));
oneOf(privateGroupFactory).parsePrivateGroup(privateGroupGroup);
will(returnValue(privateGroup));
oneOf(messageTracker).trackOutgoingMessage(txn, message);
}});
expectSendInviteMessage(msg);
expectGetLocalTimestamp(messageTimestamp);
}
@Test(expected = ProtocolStateException.class)
public void testOnInviteActionFromInvited() throws Exception {
engine.onInviteAction(txn, getDefaultSession(INVITED), null,
inviteTimestamp, signature);
}
@Test(expected = ProtocolStateException.class)
public void testOnInviteActionFromJoined() throws Exception {
engine.onInviteAction(txn, getDefaultSession(JOINED), null,
inviteTimestamp, signature);
}
@Test(expected = ProtocolStateException.class)
public void testOnInviteActionFromLeft() throws Exception {
engine.onInviteAction(txn, getDefaultSession(LEFT), null,
inviteTimestamp, signature);
}
@Test(expected = ProtocolStateException.class)
public void testOnInviteActionFromDissolved() throws Exception {
engine.onInviteAction(txn, getDefaultSession(DISSOLVED), null,
inviteTimestamp, signature);
}
@Test(expected = ProtocolStateException.class)
public void testOnInviteActionFromError() throws Exception {
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
inviteTimestamp, signature);
}
// onJoinAction
@Test(expected = UnsupportedOperationException.class)
public void testOnJoinActionFails() throws Exception {
engine.onJoinAction(txn, getDefaultSession(START));
}
// onLeaveAction
@Test
public void testOnLeaveActionFromStart() throws Exception {
CreatorSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromDissolved() throws Exception {
CreatorSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromError() throws Exception {
CreatorSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromInvited() throws Exception {
CreatorSession session = getDefaultSession(INVITED);
expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session);
assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveActionFromJoined() throws Exception {
CreatorSession session = getDefaultSession(JOINED);
expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session);
assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveActionFromLeft() throws Exception {
CreatorSession session = getDefaultSession(LEFT);
expectOnLocalLeave();
CreatorSession newSession = engine.onLeaveAction(txn, session);
assertEquals(DISSOLVED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
private void expectOnLocalLeave() throws Exception {
expectSetPrivateGroupVisibility(INVISIBLE);
expectSendLeaveMessage(false);
}
// onMemberAddedAction
@Test
public void testOnMemberAddedAction() throws Exception {
CreatorSession session = getDefaultSession(START);
assertEquals(session, engine.onMemberAddedAction(txn, session));
session = getDefaultSession(INVITED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
session = getDefaultSession(JOINED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
session = getDefaultSession(LEFT);
assertEquals(session, engine.onMemberAddedAction(txn, session));
session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
session = getDefaultSession(ERROR);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
// onInviteMessage
@Test
public void testOnInviteMessageInAnyStateWhenSubscribed() throws Exception {
expectAbortWhenSubscribedToGroup();
CreatorSession session = getDefaultSession(LEFT);
CreatorSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertEquals(ERROR, newSession.getState());
expectAbortWhenSubscribedToGroup();
session = getDefaultSession(START);
newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageInAnyStateWhenNotSubscribed()
throws Exception {
expectAbortWhenNotSubscribedToGroup();
CreatorSession session = getDefaultSession(LEFT);
CreatorSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertEquals(ERROR, newSession.getState());
expectAbortWhenNotSubscribedToGroup();
session = getDefaultSession(START);
newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
// onJoinMessage
@Test
public void testOnJoinMessageFromStart() throws Exception {
CreatorSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromJoined() throws Exception {
CreatorSession session = getDefaultSession(JOINED);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromLeft() throws Exception {
CreatorSession session = getDefaultSession(LEFT);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromInvitedWithWrongTimestamp()
throws Exception {
CreatorSession session = getDefaultSession(INVITED);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromInvitedWithInvalidDependency()
throws Exception {
CreatorSession session = getDefaultSession(INVITED);
JoinMessage invalidJoinMessage =
new JoinMessage(messageId, contactGroupId, privateGroupId,
inviteTimestamp + 1, messageId);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onJoinMessage(txn, session, invalidJoinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromInvited() throws Exception {
CreatorSession session = getDefaultSession(INVITED);
JoinMessage properJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, inviteTimestamp + 1,
lastRemoteMessageId);
expectSendJoinMessage(properJoinMessage, false);
expectMarkMessageVisibleInUi(properJoinMessage.getId(), true);
context.checking(new Expectations() {{
oneOf(messageTracker)
.trackMessage(txn, contactGroupId, inviteTimestamp + 1,
false);
}});
expectGetContactId();
expectSetPrivateGroupVisibility(SHARED);
CreatorSession newSession =
engine.onJoinMessage(txn, session, properJoinMessage);
assertEquals(JOINED, newSession.getState());
assertEquals(messageId, newSession.getLastLocalMessageId());
assertEquals(properJoinMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinMessageFromDissolved() throws Exception {
CreatorSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
}
@Test
public void testOnJoinMessageFromError() throws Exception {
CreatorSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
}
// onLeaveMessage
@Test
public void testOnLeaveMessageFromStart() throws Exception {
LeaveMessage leaveMessage =
new LeaveMessage(messageId, contactGroupId, privateGroupId,
inviteTimestamp, lastLocalMessageId);
CreatorSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromLeft() throws Exception {
CreatorSession session = getDefaultSession(LEFT);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvitedWithWrongTime() throws Exception {
CreatorSession session = getDefaultSession(INVITED);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvitedWithWrongDependency()
throws Exception {
LeaveMessage invalidLeaveMessage =
new LeaveMessage(messageId, contactGroupId, privateGroupId,
inviteTimestamp + 1, lastLocalMessageId);
CreatorSession session = getDefaultSession(INVITED);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvited()
throws Exception {
LeaveMessage properLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, inviteTimestamp + 1,
lastRemoteMessageId);
CreatorSession session = getDefaultSession(INVITED);
expectMarkMessageVisibleInUi(properLeaveMessage.getId(), true);
context.checking(new Expectations() {{
oneOf(messageTracker)
.trackMessage(txn, contactGroupId, inviteTimestamp + 1,
false);
}});
expectGetContactId();
CreatorSession newSession =
engine.onLeaveMessage(txn, session, properLeaveMessage);
assertEquals(START, newSession.getState());
assertEquals(lastLocalMessageId, newSession.getLastLocalMessageId());
assertEquals(properLeaveMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(localTimestamp, newSession.getLocalTimestamp());
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveMessageFromDissolved() throws Exception {
CreatorSession session = getDefaultSession(DISSOLVED);
assertEquals(session,
engine.onLeaveMessage(txn, session, leaveMessage));
}
@Test
public void testOnLeaveMessageFromError() throws Exception {
CreatorSession session = getDefaultSession(ERROR);
assertEquals(session,
engine.onLeaveMessage(txn, session, leaveMessage));
}
// onAbortMessage
@Test
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
CreatorSession session = getDefaultSession(START);
expectIsNotSubscribedPrivateGroup();
expectSendAbortMessage();
CreatorSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnAbortMessageWhenSubscribed() throws Exception {
CreatorSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
CreatorSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
// helper methods
private void expectAbortWhenSubscribedToGroup() throws Exception {
expectIsSubscribedPrivateGroup();
expectSetPrivateGroupVisibility(INVISIBLE);
expectSendAbortMessage();
}
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
expectIsNotSubscribedPrivateGroup();
expectSendAbortMessage();
}
private void assertSessionAborted(CreatorSession oldSession,
CreatorSession newSession) throws Exception {
assertEquals(ERROR, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(oldSession, newSession);
}
}

View File

@@ -0,0 +1,900 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.BrambleMockTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
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.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
import org.briarproject.briar.api.sharing.InvitationMessage;
import org.jmock.AbstractExpectations;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getRandomString;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class);
private final PrivateGroupFactory privateGroupFactory =
context.mock(PrivateGroupFactory.class);
private final PrivateGroupManager privateGroupManager =
context.mock(PrivateGroupManager.class);
private final MessageParser messageParser =
context.mock(MessageParser.class);
private final SessionParser sessionParser =
context.mock(SessionParser.class);
private final SessionEncoder sessionEncoder =
context.mock(SessionEncoder.class);
private final ProtocolEngineFactory engineFactory =
context.mock(ProtocolEngineFactory.class);
private final CreatorProtocolEngine creatorEngine;
private final InviteeProtocolEngine inviteeEngine;
private final PeerProtocolEngine peerEngine;
private final CreatorSession creatorSession;
private final InviteeSession inviteeSession;
private final PeerSession peerSession;
private final MessageMetadata messageMetadata;
private final GroupInvitationManagerImpl groupInvitationManager;
private final Group localGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
private final Transaction txn = new Transaction(null, false);
private final ContactId contactId = new ContactId(0);
private final Author author =
new Author(new AuthorId(getRandomId()), getRandomString(5),
getRandomBytes(5));
private final Contact contact =
new Contact(contactId, author, new AuthorId(getRandomId()), true,
true);
private final Group contactGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
private final Group privateGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
private final BdfDictionary meta = BdfDictionary.of(new BdfEntry("m", "e"));
private final Message message =
new Message(new MessageId(getRandomId()), contactGroup.getId(),
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
private final BdfList body = BdfList.of("body");
private final SessionId sessionId =
new SessionId(privateGroup.getId().getBytes());
private final Message storageMessage =
new Message(new MessageId(getRandomId()), contactGroup.getId(),
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
private final BdfDictionary bdfSession =
BdfDictionary.of(new BdfEntry("f", "o"));
private final Map<MessageId, BdfDictionary> oneResult =
Collections.singletonMap(storageMessage.getId(), bdfSession);
private final Map<MessageId, BdfDictionary> noResults =
Collections.emptyMap();
public GroupInvitationManagerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
creatorEngine = context.mock(CreatorProtocolEngine.class);
inviteeEngine = context.mock(InviteeProtocolEngine.class);
peerEngine = context.mock(PeerProtocolEngine.class);
creatorSession = context.mock(CreatorSession.class);
inviteeSession = context.mock(InviteeSession.class);
peerSession = context.mock(PeerSession.class);
messageMetadata = context.mock(MessageMetadata.class);
context.checking(new Expectations() {{
oneOf(engineFactory).createCreatorEngine();
will(returnValue(creatorEngine));
oneOf(engineFactory).createInviteeEngine();
will(returnValue(inviteeEngine));
oneOf(engineFactory).createPeerEngine();
will(returnValue(peerEngine));
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
will(returnValue(localGroup));
}});
MetadataParser metadataParser = context.mock(MetadataParser.class);
MessageTracker messageTracker = context.mock(MessageTracker.class);
groupInvitationManager =
new GroupInvitationManagerImpl(db, clientHelper, metadataParser,
messageTracker, contactGroupFactory,
privateGroupFactory, privateGroupManager, messageParser,
sessionParser, sessionEncoder, engineFactory);
}
@Test
public void testCreateLocalState() throws Exception {
context.checking(new Expectations() {{
oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact)));
}});
expectAddingContact(contact, true);
groupInvitationManager.createLocalState(txn);
}
private void expectAddingContact(final Contact c,
final boolean contactExists) throws Exception {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c);
will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(contactExists));
}});
if (contactExists) return;
final BdfDictionary meta = BdfDictionary
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, c.getId().getInt()));
context.checking(new Expectations() {{
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, c.getId(), contactGroup.getId(),
SHARED);
oneOf(clientHelper)
.mergeGroupMetadata(txn, contactGroup.getId(), meta);
oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID);
will(returnValue(Collections.singletonList(privateGroup)));
oneOf(privateGroupManager)
.isMember(txn, privateGroup.getId(), c.getAuthor());
will(returnValue(true));
}});
expectAddingMember(privateGroup.getId(), c);
}
private void expectAddingMember(final GroupId g, final Contact c)
throws Exception {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c);
will(returnValue(contactGroup));
}});
expectGetSession(noResults, new SessionId(g.getBytes()),
contactGroup.getId());
context.checking(new Expectations() {{
oneOf(peerEngine).onMemberAddedAction(with(txn),
with(any(PeerSession.class)));
will(returnValue(peerSession));
}});
expectStoreSession(peerSession, storageMessage.getId());
expectCreateStorageId();
}
private void expectCreateStorageId() throws DbException {
context.checking(new Expectations() {{
oneOf(clientHelper)
.createMessageForStoringMetadata(contactGroup.getId());
will(returnValue(storageMessage));
oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(),
false);
}});
}
private void expectStoreSession(final Session session,
final MessageId storageId) throws Exception {
context.checking(new Expectations() {{
oneOf(sessionEncoder).encodeSession(session);
will(returnValue(meta));
oneOf(clientHelper).mergeMessageMetadata(txn, storageId, meta);
}});
}
private void expectGetSession(final Map<MessageId, BdfDictionary> results,
final SessionId sessionId, final GroupId contactGroupId)
throws Exception {
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
context.checking(new Expectations() {{
oneOf(sessionParser).getSessionQuery(sessionId);
will(returnValue(query));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId, query);
will(returnValue(results));
}});
}
@Test
public void testAddingContact() throws Exception {
expectAddingContact(contact, false);
groupInvitationManager.addingContact(txn, contact);
}
@Test
public void testRemovingContact() throws Exception {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup);
}});
groupInvitationManager.removingContact(txn, contact);
}
@Test(expected = FormatException.class)
public void testIncomingUnknownMessage() throws Exception {
expectFirstIncomingMessage(Role.INVITEE, ABORT);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingFirstInviteMessage() throws Exception {
expectFirstIncomingMessage(Role.INVITEE, INVITE);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingFirstJoinMessage() throws Exception {
expectFirstIncomingMessage(Role.PEER, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingInviteMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, INVITE);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingJoinMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingJoinMessageForCreator() throws Exception {
expectIncomingMessage(Role.CREATOR, JOIN);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingLeaveMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, LEAVE);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
@Test
public void testIncomingAbortMessage() throws Exception {
expectIncomingMessage(Role.INVITEE, ABORT);
groupInvitationManager.incomingMessage(txn, message, body, meta);
}
private void expectFirstIncomingMessage(Role role, MessageType type)
throws Exception {
expectParseMessageMetadata();
expectGetSession(noResults, sessionId, contactGroup.getId());
Session session = expectHandleFirstMessage(role, messageMetadata, type);
if (session != null) {
expectCreateStorageId();
expectStoreSession(session, storageMessage.getId());
}
}
private void expectParseMessageMetadata() throws Exception {
context.checking(new Expectations() {{
oneOf(messageParser).parseMetadata(meta);
will(returnValue(messageMetadata));
oneOf(messageMetadata).getPrivateGroupId();
will(returnValue(privateGroup.getId()));
}});
}
private void expectIncomingMessage(Role role, MessageType type)
throws Exception {
BdfDictionary bdfSession = BdfDictionary.of(new BdfEntry("f", "o"));
expectIncomingMessageWithSession(role, type, bdfSession);
}
private void expectIncomingMessageWithSession(final Role role,
final MessageType type, final BdfDictionary bdfSession)
throws Exception {
expectParseMessageMetadata();
expectGetSession(oneResult, sessionId, contactGroup.getId());
Session session = expectHandleMessage(role, messageMetadata, bdfSession,
type);
expectStoreSession(session, storageMessage.getId());
}
@Nullable
private Session expectHandleFirstMessage(Role role,
final MessageMetadata messageMetadata, final MessageType type)
throws Exception {
context.checking(new Expectations() {{
oneOf(messageMetadata).getPrivateGroupId();
will(returnValue(privateGroup.getId()));
oneOf(messageMetadata).getMessageType();
will(returnValue(type));
}});
if (type == ABORT || type == LEAVE) return null;
AbstractProtocolEngine engine;
Session session;
if (type == INVITE) {
assertEquals(Role.INVITEE, role);
engine = inviteeEngine;
session = inviteeSession;
} else if (type == JOIN) {
assertEquals(Role.PEER, role);
engine = peerEngine;
session = peerSession;
} else {
throw new AssertionError();
}
expectIndividualMessage(type, engine, session);
return session;
}
@Nullable
private Session expectHandleMessage(final Role role,
final MessageMetadata messageMetadata, final BdfDictionary state,
final MessageType type) throws Exception {
context.checking(new Expectations() {{
oneOf(messageMetadata).getMessageType();
will(returnValue(type));
oneOf(sessionParser).getRole(state);
will(returnValue(role));
}});
if (role == Role.CREATOR) {
context.checking(new Expectations() {{
oneOf(sessionParser)
.parseCreatorSession(contactGroup.getId(), state);
will(returnValue(creatorSession));
}});
expectIndividualMessage(type, creatorEngine, creatorSession);
return creatorSession;
} else if (role == Role.INVITEE) {
context.checking(new Expectations() {{
oneOf(sessionParser)
.parseInviteeSession(contactGroup.getId(), state);
will(returnValue(inviteeSession));
}});
expectIndividualMessage(type, inviteeEngine, inviteeSession);
return inviteeSession;
} else if (role == Role.PEER) {
context.checking(new Expectations() {{
oneOf(sessionParser)
.parsePeerSession(contactGroup.getId(), state);
will(returnValue(peerSession));
}});
expectIndividualMessage(type, peerEngine, peerSession);
return peerSession;
} else {
throw new AssertionError();
}
}
private <S extends Session> void expectIndividualMessage(
final MessageType type, final ProtocolEngine<S> engine,
final S session) throws Exception {
if (type == INVITE) {
final InviteMessage msg = context.mock(InviteMessage.class);
context.checking(new Expectations() {{
oneOf(messageParser).parseInviteMessage(message, body);
will(returnValue(msg));
oneOf(engine).onInviteMessage(with(txn),
with(AbstractExpectations.<S>anything()), with(msg));
will(returnValue(session));
}});
} else if (type == JOIN) {
final JoinMessage msg = context.mock(JoinMessage.class);
context.checking(new Expectations() {{
oneOf(messageParser).parseJoinMessage(message, body);
will(returnValue(msg));
oneOf(engine).onJoinMessage(with(txn),
with(AbstractExpectations.<S>anything()), with(msg));
will(returnValue(session));
}});
} else if (type == LEAVE) {
final LeaveMessage msg = context.mock(LeaveMessage.class);
context.checking(new Expectations() {{
oneOf(messageParser).parseLeaveMessage(message, body);
will(returnValue(msg));
oneOf(engine).onLeaveMessage(with(txn),
with(AbstractExpectations.<S>anything()), with(msg));
will(returnValue(session));
}});
} else if (type == ABORT) {
final AbortMessage msg = context.mock(AbortMessage.class);
context.checking(new Expectations() {{
oneOf(messageParser).parseAbortMessage(message, body);
will(returnValue(msg));
oneOf(engine).onAbortMessage(with(txn),
with(AbstractExpectations.<S>anything()), with(msg));
will(returnValue(session));
}});
} else {
fail();
}
}
@Test
public void testSendFirstInvitation() throws Exception {
final String msg = "Invitation text for first invitation";
final long time = 42L;
final byte[] signature = getRandomBytes(42);
expectGetSession(noResults, sessionId, contactGroup.getId());
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
}});
expectCreateStorageId();
context.checking(new Expectations() {{
oneOf(creatorEngine).onInviteAction(with(txn),
with(any(CreatorSession.class)), with(msg), with(time),
with(signature));
will(returnValue(creatorSession));
}});
expectStoreSession(creatorSession, storageMessage.getId());
context.checking(new Expectations() {{
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
groupInvitationManager.sendInvitation(privateGroup.getId(), contactId,
msg, time, signature);
}
@Test
public void testSendSubsequentInvitation() throws Exception {
final String msg = "Invitation text for subsequent invitation";
final long time = 43L;
final byte[] signature = getRandomBytes(43);
expectGetSession(oneResult, sessionId, contactGroup.getId());
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.parseCreatorSession(contactGroup.getId(), bdfSession);
will(returnValue(creatorSession));
oneOf(creatorEngine).onInviteAction(with(txn),
with(any(CreatorSession.class)), with(msg), with(time),
with(signature));
will(returnValue(creatorSession));
}});
expectStoreSession(creatorSession, storageMessage.getId());
context.checking(new Expectations() {{
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
groupInvitationManager.sendInvitation(privateGroup.getId(), contactId,
msg, time, signature);
}
@Test(expected = IllegalArgumentException.class)
public void testRespondToInvitationWithoutSession() throws Exception {
final SessionId sessionId = new SessionId(getRandomId());
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).endTransaction(txn);
}});
expectGetSession(noResults, sessionId, contactGroup.getId());
groupInvitationManager.respondToInvitation(contactId, sessionId, true);
}
@Test
public void testAcceptInvitationWithSession() throws Exception {
expectRespondToInvitation(sessionId, true);
groupInvitationManager
.respondToInvitation(contactId, sessionId, true);
}
@Test
public void testDeclineInvitationWithSession() throws Exception {
expectRespondToInvitation(sessionId, false);
groupInvitationManager
.respondToInvitation(contactId, sessionId, false);
}
@Test
public void testAcceptInvitationWithGroupId() throws Exception {
PrivateGroup pg = new PrivateGroup(privateGroup,
getRandomString(MAX_GROUP_NAME_LENGTH), author,
getRandomBytes(GROUP_SALT_LENGTH));
expectRespondToInvitation(sessionId, true);
groupInvitationManager.respondToInvitation(contactId, pg, true);
}
@Test
public void testDeclineInvitationWithGroupId() throws Exception {
PrivateGroup pg = new PrivateGroup(privateGroup,
getRandomString(MAX_GROUP_NAME_LENGTH), author,
getRandomBytes(GROUP_SALT_LENGTH));
expectRespondToInvitation(sessionId, false);
groupInvitationManager.respondToInvitation(contactId, pg, false);
}
private void expectRespondToInvitation(final SessionId sessionId,
final boolean accept) throws Exception {
expectGetSession(oneResult, sessionId, contactGroup.getId());
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.parseInviteeSession(contactGroup.getId(), bdfSession);
will(returnValue(inviteeSession));
if (accept) oneOf(inviteeEngine).onJoinAction(txn, inviteeSession);
else oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession);
will(returnValue(inviteeSession));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
expectStoreSession(inviteeSession, storageMessage.getId());
}
@Test
public void testRevealRelationship() throws Exception {
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.parsePeerSession(contactGroup.getId(), bdfSession);
will(returnValue(peerSession));
oneOf(peerEngine).onJoinAction(txn, peerSession);
will(returnValue(peerSession));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
expectGetSession(oneResult, sessionId, contactGroup.getId());
expectStoreSession(peerSession, storageMessage.getId());
groupInvitationManager
.revealRelationship(contactId, privateGroup.getId());
}
@Test(expected = IllegalArgumentException.class)
public void testRevealRelationshipWithoutSession() throws Exception {
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).endTransaction(txn);
}});
expectGetSession(noResults, sessionId, contactGroup.getId());
groupInvitationManager
.revealRelationship(contactId, privateGroup.getId());
}
@Test
public void testGetInvitationMessages() throws Exception {
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
final BdfDictionary meta2 = BdfDictionary.of(new BdfEntry("m2", "e"));
final Map<MessageId, BdfDictionary> results =
new HashMap<MessageId, BdfDictionary>();
results.put(message.getId(), meta);
results.put(messageId2, meta2);
long time1 = 1L, time2 = 2L;
final MessageMetadata messageMetadata1 =
new MessageMetadata(INVITE, privateGroup.getId(), time1, true,
true, true, true);
final MessageMetadata messageMetadata2 =
new MessageMetadata(JOIN, privateGroup.getId(), time2, true,
true, true, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(messageParser).getMessagesVisibleInUiQuery();
will(returnValue(query));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId(), query);
will(returnValue(results));
// first message
oneOf(messageParser).parseMetadata(meta);
will(returnValue(messageMetadata1));
oneOf(db).getMessageStatus(txn, contactId, message.getId());
oneOf(clientHelper).getMessage(txn, message.getId());
will(returnValue(message));
oneOf(clientHelper).toList(message);
will(returnValue(body));
oneOf(messageParser).parseInviteMessage(message, body);
// second message
oneOf(messageParser).parseMetadata(meta2);
will(returnValue(messageMetadata2));
oneOf(db).getMessageStatus(txn, contactId, messageId2);
// end transaction
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
Collection<InvitationMessage> messages =
groupInvitationManager.getInvitationMessages(contactId);
assertEquals(2, messages.size());
for (InvitationMessage m : messages) {
assertEquals(contactGroup.getId(), m.getGroupId());
assertEquals(contactId, m.getContactId());
if (m.getId().equals(message.getId())) {
assertTrue(m instanceof GroupInvitationRequest);
assertEquals(time1, m.getTimestamp());
} else if (m.getId().equals(messageId2)) {
assertTrue(m instanceof GroupInvitationResponse);
assertEquals(time2, m.getTimestamp());
} else {
throw new AssertionError();
}
}
}
@Test
public void testGetInvitations() throws Exception {
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
final BdfDictionary meta2 = BdfDictionary.of(new BdfEntry("m2", "e"));
final Map<MessageId, BdfDictionary> results =
new HashMap<MessageId, BdfDictionary>();
results.put(message.getId(), meta);
results.put(messageId2, meta2);
final Message message2 = new Message(messageId2, contactGroup.getId(),
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
long time1 = 1L, time2 = 2L;
final String groupName = getRandomString(MAX_GROUP_NAME_LENGTH);
final byte[] salt = getRandomBytes(GROUP_SALT_LENGTH);
final InviteMessage inviteMessage1 =
new InviteMessage(message.getId(), contactGroup.getId(),
privateGroup.getId(), time1, groupName, author, salt,
null, getRandomBytes(5));
final InviteMessage inviteMessage2 =
new InviteMessage(message.getId(), contactGroup.getId(),
privateGroup.getId(), time2, groupName, author, salt,
null, getRandomBytes(5));
final PrivateGroup pg = new PrivateGroup(privateGroup, groupName,
author, salt);
final BdfList body2 = BdfList.of("body2");
context.checking(new Expectations() {{
oneOf(messageParser).getInvitesAvailableToAnswerQuery();
will(returnValue(query));
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId(), query);
will(returnValue(results));
// message 1
oneOf(clientHelper).getMessage(txn, message.getId());
will(returnValue(message));
oneOf(clientHelper).toList(message);
will(returnValue(body));
oneOf(messageParser).parseInviteMessage(message, body);
will(returnValue(inviteMessage1));
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
salt);
will(returnValue(pg));
// message 2
oneOf(clientHelper).getMessage(txn, messageId2);
will(returnValue(message2));
oneOf(clientHelper).toList(message2);
will(returnValue(body2));
oneOf(messageParser).parseInviteMessage(message2, body2);
will(returnValue(inviteMessage2));
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
salt);
will(returnValue(pg));
// end transaction
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
Collection<GroupInvitationItem> items =
groupInvitationManager.getInvitations();
assertEquals(2, items.size());
for (GroupInvitationItem i : items) {
assertEquals(contact, i.getCreator());
assertEquals(author, i.getCreator().getAuthor());
assertEquals(privateGroup.getId(), i.getId());
assertEquals(groupName, i.getName());
}
}
@Test
public void testIsInvitationAllowed() throws Exception {
expectIsInvitationAllowed(CreatorState.START);
assertTrue(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
}
@Test
public void testIsNotInvitationAllowed() throws Exception {
expectIsInvitationAllowed(CreatorState.DISSOLVED);
assertFalse(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
expectIsInvitationAllowed(CreatorState.ERROR);
assertFalse(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
expectIsInvitationAllowed(CreatorState.INVITED);
assertFalse(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
expectIsInvitationAllowed(CreatorState.JOINED);
assertFalse(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
expectIsInvitationAllowed(CreatorState.LEFT);
assertFalse(groupInvitationManager
.isInvitationAllowed(contact, privateGroup.getId()));
}
private void expectIsInvitationAllowed(final CreatorState state)
throws Exception {
expectGetSession(oneResult, sessionId, contactGroup.getId());
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(sessionParser)
.parseCreatorSession(contactGroup.getId(), bdfSession);
will(returnValue(creatorSession));
oneOf(creatorSession).getState();
will(returnValue(state));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
}
@Test
public void testAddingMember() throws Exception {
expectAddingMember(privateGroup.getId(), contact);
context.checking(new Expectations() {{
oneOf(db).getContactsByAuthorId(txn, author.getId());
will(returnValue(Collections.singletonList(contact)));
}});
groupInvitationManager.addingMember(txn, privateGroup.getId(), author);
}
@Test
public void testRemovingGroupEndsSessions() throws Exception {
final Contact contact2 = new Contact(new ContactId(2), author,
author.getId(), true, true);
final Contact contact3 = new Contact(new ContactId(3), author,
author.getId(), true, true);
final Collection<Contact> contacts =
Arrays.asList(contact, contact2, contact3);
final Group contactGroup2 = new Group(new GroupId(getRandomId()),
CLIENT_ID, getRandomBytes(5));
final Group contactGroup3 = new Group(new GroupId(getRandomId()),
CLIENT_ID, getRandomBytes(5));
final MessageId storageId2 = new MessageId(getRandomId());
final MessageId storageId3 = new MessageId(getRandomId());
final BdfDictionary bdfSession2 =
BdfDictionary.of(new BdfEntry("f2", "o"));
final BdfDictionary bdfSession3 =
BdfDictionary.of(new BdfEntry("f3", "o"));
expectGetSession(oneResult, sessionId, contactGroup.getId());
expectGetSession(Collections.singletonMap(storageId2, bdfSession2),
sessionId, contactGroup2.getId());
expectGetSession(Collections.singletonMap(storageId3, bdfSession3),
sessionId, contactGroup3.getId());
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
will(returnValue(contactGroup2));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact3);
will(returnValue(contactGroup3));
// session 1
oneOf(sessionParser).getRole(bdfSession);
will(returnValue(Role.CREATOR));
oneOf(sessionParser)
.parseCreatorSession(contactGroup.getId(), bdfSession);
will(returnValue(creatorSession));
oneOf(creatorEngine).onLeaveAction(txn, creatorSession);
will(returnValue(creatorSession));
// session 2
oneOf(sessionParser).getRole(bdfSession2);
will(returnValue(Role.INVITEE));
oneOf(sessionParser)
.parseInviteeSession(contactGroup2.getId(), bdfSession2);
will(returnValue(inviteeSession));
oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession);
will(returnValue(inviteeSession));
// session 3
oneOf(sessionParser).getRole(bdfSession3);
will(returnValue(Role.PEER));
oneOf(sessionParser)
.parsePeerSession(contactGroup3.getId(), bdfSession3);
will(returnValue(peerSession));
oneOf(peerEngine).onLeaveAction(txn, peerSession);
will(returnValue(peerSession));
}});
expectStoreSession(creatorSession, storageMessage.getId());
expectStoreSession(inviteeSession, storageId2);
expectStoreSession(peerSession, storageId3);
groupInvitationManager.removingGroup(txn, privateGroup.getId());
}
}

View File

@@ -0,0 +1,582 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.ValidatorTestCase;
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.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.jmock.Expectations;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getRandomString;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
import static org.briarproject.briar.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);
}
}

View File

@@ -0,0 +1,771 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.privategroup.GroupMessage;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Collections;
import java.util.Map;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.briar.privategroup.invitation.InviteeState.ACCEPTED;
import static org.briarproject.briar.privategroup.invitation.InviteeState.DISSOLVED;
import static org.briarproject.briar.privategroup.invitation.InviteeState.ERROR;
import static org.briarproject.briar.privategroup.invitation.InviteeState.INVITED;
import static org.briarproject.briar.privategroup.invitation.InviteeState.JOINED;
import static org.briarproject.briar.privategroup.invitation.InviteeState.LEFT;
import static org.briarproject.briar.privategroup.invitation.InviteeState.START;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
private final InviteeProtocolEngine engine =
new InviteeProtocolEngine(db, clientHelper, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock);
private final LocalAuthor localAuthor =
new LocalAuthor(new AuthorId(getRandomId()), "Local Author",
getRandomBytes(12), getRandomBytes(12), 42L);
private InviteeSession getDefaultSession(InviteeState state) {
return new InviteeSession(contactGroupId, privateGroupId,
lastLocalMessageId, lastRemoteMessageId, localTimestamp,
inviteTimestamp, state);
}
// onInviteAction
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromStart() throws Exception {
engine.onInviteAction(txn, getDefaultSession(START), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromLeft() throws Exception {
engine.onInviteAction(txn, getDefaultSession(ACCEPTED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromInvited() throws Exception {
engine.onInviteAction(txn, getDefaultSession(INVITED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromDissolved() throws Exception {
engine.onInviteAction(txn, getDefaultSession(DISSOLVED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromAccepted() throws Exception {
engine.onInviteAction(txn, getDefaultSession(ACCEPTED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromJoined() throws Exception {
engine.onInviteAction(txn, getDefaultSession(JOINED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromError() throws Exception {
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
messageTimestamp, signature);
}
// onJoinAction
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromStart() throws Exception {
engine.onJoinAction(txn, getDefaultSession(START));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromAccepted() throws Exception {
engine.onJoinAction(txn, getDefaultSession(ACCEPTED));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromJoined() throws Exception {
engine.onJoinAction(txn, getDefaultSession(JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromLeft() throws Exception {
engine.onJoinAction(txn, getDefaultSession(LEFT));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromDissolved() throws Exception {
engine.onJoinAction(txn, getDefaultSession(DISSOLVED));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromError() throws Exception {
engine.onJoinAction(txn, getDefaultSession(ERROR));
}
@Test
public void testOnJoinActionFromInvited() throws Exception {
final JoinMessage properJoinMessage =
new JoinMessage(messageId, contactGroupId, privateGroupId,
messageTimestamp, lastRemoteMessageId);
final Message inviteMsg =
new Message(lastRemoteMessageId, contactGroupId, 1337L,
getRandomBytes(42));
final BdfList inviteList = BdfList.of("inviteMessage");
final long timestamp = 0L;
final GroupMessage joinGroupMessage =
new GroupMessage(message, null, localAuthor);
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
expectSendJoinMessage(properJoinMessage, true);
context.checking(new Expectations() {{
oneOf(messageTracker).trackOutgoingMessage(txn, message);
oneOf(clientHelper).getMessage(txn, lastRemoteMessageId);
will(returnValue(inviteMsg));
oneOf(clientHelper).toList(inviteMsg);
will(returnValue(inviteList));
oneOf(messageParser).parseInviteMessage(inviteMsg, inviteList);
will(returnValue(inviteMessage));
oneOf(privateGroupFactory)
.createPrivateGroup(inviteMessage.getGroupName(),
inviteMessage.getCreator(),
inviteMessage.getSalt());
will(returnValue(privateGroup));
oneOf(clock).currentTimeMillis();
will((returnValue(timestamp)));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(groupMessageFactory).createJoinMessage(privateGroupId,
inviteMessage.getTimestamp() + 1, localAuthor,
inviteMessage.getTimestamp(), inviteMessage.getSignature());
will(returnValue(joinGroupMessage));
oneOf(privateGroupManager)
.addPrivateGroup(txn, privateGroup, joinGroupMessage,
false);
}});
expectSetPrivateGroupVisibility(VISIBLE);
InviteeSession session = getDefaultSession(INVITED);
InviteeSession newSession = engine.onJoinAction(txn, session);
assertEquals(ACCEPTED, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test(expected = IllegalStateException.class)
public void testOnJoinActionFromInvitedWithoutInvitationId()
throws Exception {
InviteeSession session =
new InviteeSession(contactGroupId, privateGroupId,
lastLocalMessageId, null, localTimestamp,
inviteTimestamp, INVITED);
engine.onJoinAction(txn, session);
}
// onLeaveAction
@Test
public void testOnLeaveActionFromStart() throws Exception {
InviteeSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromLeft() throws Exception {
InviteeSession session = getDefaultSession(LEFT);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromDissolved() throws Exception {
InviteeSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromError() throws Exception {
InviteeSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromInvited() throws Exception {
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
expectSendLeaveMessage(true);
context.checking(new Expectations() {{
oneOf(messageTracker).trackOutgoingMessage(txn, message);
}});
InviteeSession session = getDefaultSession(INVITED);
InviteeSession newSession = engine.onLeaveAction(txn, session);
assertEquals(START, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test(expected = IllegalStateException.class)
public void testOnLeaveActionFromInvitedWithoutInvitationId()
throws Exception {
InviteeSession session =
new InviteeSession(contactGroupId, privateGroupId,
lastLocalMessageId, null, localTimestamp,
inviteTimestamp, INVITED);
engine.onJoinAction(txn, session);
}
@Test
public void testOnLeaveActionFromAccepted() throws Exception {
expectSendLeaveMessage(false);
InviteeSession session = getDefaultSession(ACCEPTED);
InviteeSession newSession = engine.onLeaveAction(txn, session);
assertEquals(LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveActionFromJoined() throws Exception {
expectSendLeaveMessage(false);
InviteeSession session = getDefaultSession(JOINED);
InviteeSession newSession = engine.onLeaveAction(txn, session);
assertEquals(LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
// onMemberAddedAction
@Test
public void testOnMemberAddedFromStart() throws Exception {
InviteeSession session = getDefaultSession(START);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromInvited() throws Exception {
InviteeSession session = getDefaultSession(INVITED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromAccepted() throws Exception {
InviteeSession session = getDefaultSession(ACCEPTED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromJoined() throws Exception {
InviteeSession session = getDefaultSession(JOINED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromLeft() throws Exception {
InviteeSession session = getDefaultSession(LEFT);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromDissolved() throws Exception {
InviteeSession session = getDefaultSession(DISSOLVED);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
@Test
public void testOnMemberAddedFromError() throws Exception {
InviteeSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
// onInviteMessage
@Test
public void testOnInviteMessageFromStartWithLowerTimestamp()
throws Exception {
InviteeSession session = getDefaultSession(START);
assertTrue(
inviteMessage.getTimestamp() <= session.getInviteTimestamp());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromStartButNotCreator() throws Exception {
InviteeSession session = getDefaultSession(START);
InviteMessage properInviteMessage =
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
privateGroup.getName(), privateGroup.getCreator(),
privateGroup.getSalt(), "msg", signature);
Author notCreator =
new Author(new AuthorId(getRandomId()), "Not Creator",
getRandomBytes(5));
final Contact notCreatorContact =
new Contact(contactId, notCreator, localAuthor.getId(), true,
true);
expectGetContactId();
context.checking(new Expectations() {{
oneOf(db).getContact(txn, contactId);
will(returnValue(notCreatorContact));
}});
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onInviteMessage(txn, session, properInviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromStart() throws Exception {
InviteeSession session = getDefaultSession(START);
final InviteMessage properInviteMessage =
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
privateGroup.getName(), privateGroup.getCreator(),
privateGroup.getSalt(), "msg", signature);
assertEquals(contact.getAuthor(), privateGroup.getCreator());
expectGetContactId();
context.checking(new Expectations() {{
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
}});
expectMarkMessageVisibleInUi(properInviteMessage.getId(), true);
expectMarkMessageAvailableToAnswer(properInviteMessage.getId(), true);
context.checking(new Expectations() {{
oneOf(messageTracker).trackMessage(txn, contactGroupId,
properInviteMessage.getTimestamp(), false);
oneOf(privateGroupFactory)
.createPrivateGroup(properInviteMessage.getGroupName(),
properInviteMessage.getCreator(),
properInviteMessage.getSalt());
will(returnValue(privateGroup));
}});
InviteeSession newSession =
engine.onInviteMessage(txn, session, properInviteMessage);
assertEquals(INVITED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(properInviteMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertEquals(properInviteMessage.getTimestamp(),
newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnInviteMessageFromInvited() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(INVITED);
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromAccepted() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(ACCEPTED);
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromJoined() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(JOINED);
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromLeft() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(LEFT);
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromDissolved() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(DISSOLVED);
InviteeSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromError() throws Exception {
InviteeSession session = getDefaultSession(ERROR);
assertEquals(session,
engine.onInviteMessage(txn, session, inviteMessage));
}
// onJoinMessage
@Test
public void testOnJoinMessageFromStart() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(START);
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromInvited() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(INVITED);
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromJoined() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(JOINED);
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromLeft() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(LEFT);
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromDissolved() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(DISSOLVED);
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromAcceptedWithWrongTimestamp()
throws Exception {
InviteeSession session = getDefaultSession(ACCEPTED);
assertTrue(joinMessage.getTimestamp() <= session.getInviteTimestamp());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromAcceptedWithInvalidDependency()
throws Exception {
InviteeSession session = getDefaultSession(ACCEPTED);
JoinMessage invalidJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
lastLocalMessageId);
assertFalse(invalidJoinMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNotNull(session.getLastRemoteMessageId());
assertNotNull(invalidJoinMessage.getPreviousMessageId());
assertFalse(session.getLastRemoteMessageId()
.equals(invalidJoinMessage.getPreviousMessageId()));
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onJoinMessage(txn, session, invalidJoinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromAccepted() throws Exception {
InviteeSession session = getDefaultSession(ACCEPTED);
JoinMessage properJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
lastRemoteMessageId);
assertFalse(properJoinMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNotNull(session.getLastRemoteMessageId());
assertNotNull(properJoinMessage.getPreviousMessageId());
assertTrue(session.getLastRemoteMessageId()
.equals(properJoinMessage.getPreviousMessageId()));
expectSetPrivateGroupVisibility(SHARED);
InviteeSession newSession =
engine.onJoinMessage(txn, session, properJoinMessage);
assertEquals(JOINED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(properJoinMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertEquals(session.getInviteTimestamp(),
newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinMessageFromFromError() throws Exception {
InviteeSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
}
// onLeaveMessage
@Test
public void testOnLeaveMessageFromStart() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(START);
InviteeSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromDissolved() throws Exception {
expectAbortWhenSubscribedToGroup();
InviteeSession session = getDefaultSession(DISSOLVED);
InviteeSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvitedWithWrongTimestamp()
throws Exception {
InviteeSession session = getDefaultSession(INVITED);
assertTrue(leaveMessage.getTimestamp() <= session.getInviteTimestamp());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromLeftWithWrongTimestamp()
throws Exception {
InviteeSession session = getDefaultSession(LEFT);
assertTrue(leaveMessage.getTimestamp() <= session.getInviteTimestamp());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvitedWithInvalidDependency()
throws Exception {
InviteeSession session = getDefaultSession(INVITED);
LeaveMessage invalidLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1, null);
assertFalse(invalidLeaveMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNull(invalidLeaveMessage.getPreviousMessageId());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromLeftWithInvalidDependency()
throws Exception {
InviteeSession session = getDefaultSession(LEFT);
LeaveMessage invalidLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1, null);
assertFalse(invalidLeaveMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNull(invalidLeaveMessage.getPreviousMessageId());
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromInvited() throws Exception {
InviteeSession session = getDefaultSession(INVITED);
LeaveMessage properLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
lastRemoteMessageId);
assertFalse(properLeaveMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNotNull(session.getLastRemoteMessageId());
assertNotNull(properLeaveMessage.getPreviousMessageId());
assertTrue(session.getLastRemoteMessageId()
.equals(properLeaveMessage.getPreviousMessageId()));
InviteeSession newSession =
engine.onLeaveMessage(txn, session, properLeaveMessage);
assertEquals(DISSOLVED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(properLeaveMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertEquals(session.getInviteTimestamp(),
newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveMessageFromLeft() throws Exception {
InviteeSession session = getDefaultSession(LEFT);
LeaveMessage properLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, session.getInviteTimestamp() + 1,
lastRemoteMessageId);
assertFalse(properLeaveMessage.getTimestamp() <=
session.getInviteTimestamp());
assertNotNull(session.getLastRemoteMessageId());
assertNotNull(properLeaveMessage.getPreviousMessageId());
assertTrue(session.getLastRemoteMessageId()
.equals(properLeaveMessage.getPreviousMessageId()));
InviteeSession newSession =
engine.onLeaveMessage(txn, session, properLeaveMessage);
assertEquals(DISSOLVED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(properLeaveMessage.getId(),
newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertEquals(session.getInviteTimestamp(),
newSession.getInviteTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
// onAbortMessage
@Test
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
InviteeSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
InviteeSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnAbortMessageWhenSubscribed() throws Exception {
InviteeSession session = getDefaultSession(START);
expectAbortWhenNotSubscribedToGroup();
InviteeSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
// helper methods
private void expectMarkMessageAvailableToAnswer(final MessageId id,
final boolean available) throws Exception {
final BdfDictionary meta = new BdfDictionary();
context.checking(new Expectations() {{
oneOf(messageEncoder)
.setAvailableToAnswer(meta, available);
oneOf(clientHelper)
.mergeMessageMetadata(txn, id, meta);
}});
}
private void expectAbortWhenSubscribedToGroup() throws Exception {
expectAbort(true);
}
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
expectAbort(false);
}
private void expectAbort(boolean subscribed) throws Exception {
final BdfDictionary query = BdfDictionary.of(new BdfEntry("query", ""));
final BdfDictionary meta = BdfDictionary.of(new BdfEntry("meta", ""));
final Map<MessageId, BdfDictionary> invites =
Collections.singletonMap(lastRemoteMessageId, meta);
context.checking(new Expectations() {{
oneOf(messageParser)
.getInvitesAvailableToAnswerQuery(privateGroupId);
will(returnValue(query));
oneOf(clientHelper)
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
will(returnValue(invites));
}});
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
if (subscribed) {
expectIsSubscribedPrivateGroup();
expectSetPrivateGroupVisibility(INVISIBLE);
} else {
expectIsNotSubscribedPrivateGroup();
}
expectSendAbortMessage();
}
private void assertSessionAborted(InviteeSession oldSession,
InviteeSession newSession) throws Exception {
assertEquals(ERROR, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(oldSession, newSession);
}
}

View File

@@ -0,0 +1,701 @@
package org.briarproject.briar.privategroup.invitation;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.briar.privategroup.invitation.PeerState.AWAIT_MEMBER;
import static org.briarproject.briar.privategroup.invitation.PeerState.BOTH_JOINED;
import static org.briarproject.briar.privategroup.invitation.PeerState.ERROR;
import static org.briarproject.briar.privategroup.invitation.PeerState.LOCAL_JOINED;
import static org.briarproject.briar.privategroup.invitation.PeerState.LOCAL_LEFT;
import static org.briarproject.briar.privategroup.invitation.PeerState.NEITHER_JOINED;
import static org.briarproject.briar.privategroup.invitation.PeerState.START;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class PeerProtocolEngineTest extends AbstractProtocolEngineTest {
private final PeerProtocolEngine engine =
new PeerProtocolEngine(db, clientHelper, privateGroupManager,
privateGroupFactory, groupMessageFactory, identityManager,
messageParser, messageEncoder, messageTracker, clock);
private PeerSession getDefaultSession(PeerState state) {
return new PeerSession(contactGroupId, privateGroupId,
lastLocalMessageId, lastRemoteMessageId, localTimestamp, state);
}
// onInviteAction
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromStart() throws Exception {
engine.onInviteAction(txn, getDefaultSession(START), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromAwaitMember() throws Exception {
engine.onInviteAction(txn, getDefaultSession(AWAIT_MEMBER), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromNeitherJoined() throws Exception {
engine.onInviteAction(txn, getDefaultSession(NEITHER_JOINED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromLocalJoined() throws Exception {
engine.onInviteAction(txn, getDefaultSession(LOCAL_JOINED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromBothJoined() throws Exception {
engine.onInviteAction(txn, getDefaultSession(BOTH_JOINED), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromLocalLeft() throws Exception {
engine.onInviteAction(txn, getDefaultSession(LOCAL_LEFT), null,
messageTimestamp, signature);
}
@Test(expected = UnsupportedOperationException.class)
public void testOnInviteActionFromError() throws Exception {
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
messageTimestamp, signature);
}
// onJoinAction
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromStart() throws Exception {
engine.onJoinAction(txn, getDefaultSession(START));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromAwaitMember() throws Exception {
engine.onJoinAction(txn, getDefaultSession(AWAIT_MEMBER));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromLocalJoined() throws Exception {
engine.onJoinAction(txn, getDefaultSession(LOCAL_JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromBothJoined() throws Exception {
engine.onJoinAction(txn, getDefaultSession(BOTH_JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnJoinActionFromError() throws Exception {
engine.onJoinAction(txn, getDefaultSession(ERROR));
}
@Test
public void testOnJoinActionFromNeitherJoined() throws Exception {
JoinMessage joinMessage =
new JoinMessage(messageId, contactGroupId,
privateGroupId, messageTimestamp, lastRemoteMessageId);
PeerSession session = getDefaultSession(NEITHER_JOINED);
expectSendJoinMessage(joinMessage, false);
expectSetPrivateGroupVisibility(VISIBLE);
PeerSession newSession = engine.onJoinAction(txn, session);
assertEquals(LOCAL_JOINED, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinActionFromLocalLeft() throws Exception {
JoinMessage joinMessage =
new JoinMessage(messageId, contactGroupId,
privateGroupId, messageTimestamp, lastRemoteMessageId);
PeerSession session = getDefaultSession(LOCAL_LEFT);
expectSendJoinMessage(joinMessage, false);
expectSetPrivateGroupVisibility(SHARED);
PeerSession newSession = engine.onJoinAction(txn, session);
assertEquals(BOTH_JOINED, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
// onLeaveAction
@Test
public void testOnLeaveActionFromStart() throws Exception {
PeerSession session = getDefaultSession(START);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromAwaitMember() throws Exception {
PeerSession session = getDefaultSession(AWAIT_MEMBER);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromNeitherJoined() throws Exception {
PeerSession session = getDefaultSession(NEITHER_JOINED);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromLocalLeft() throws Exception {
PeerSession session = getDefaultSession(LOCAL_LEFT);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onLeaveAction(txn, session));
}
@Test
public void testOnLeaveActionFromLocalJoined() throws Exception {
PeerSession session = getDefaultSession(LOCAL_JOINED);
expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE);
PeerSession newSession = engine.onLeaveAction(txn, session);
assertEquals(NEITHER_JOINED, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveActionFromBothJoined() throws Exception {
PeerSession session = getDefaultSession(BOTH_JOINED);
expectSendLeaveMessage(false);
expectSetPrivateGroupVisibility(INVISIBLE);
PeerSession newSession = engine.onLeaveAction(txn, session);
assertEquals(LOCAL_LEFT, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
// onMemberAddedAction
@Test
public void testOnMemberAddedFromStart() throws Exception {
PeerSession session = getDefaultSession(START);
PeerSession newSession = engine.onMemberAddedAction(txn, session);
assertEquals(NEITHER_JOINED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(session.getLastRemoteMessageId(),
newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnMemberAddedFromAwaitMember() throws Exception {
JoinMessage joinMessage =
new JoinMessage(messageId, contactGroupId,
privateGroupId, messageTimestamp, lastRemoteMessageId);
PeerSession session = getDefaultSession(AWAIT_MEMBER);
expectSendJoinMessage(joinMessage, false);
expectSetPrivateGroupVisibility(SHARED);
expectRelationshipRevealed(true);
PeerSession newSession = engine.onMemberAddedAction(txn, session);
assertEquals(BOTH_JOINED, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(session, newSession);
}
@Test(expected = ProtocolStateException.class)
public void testOnMemberAddedFromNeitherJoined() throws Exception {
engine.onMemberAddedAction(txn, getDefaultSession(NEITHER_JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnMemberAddedFromLocalJoined() throws Exception {
engine.onMemberAddedAction(txn, getDefaultSession(LOCAL_JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnMemberAddedFromBothJoined() throws Exception {
engine.onMemberAddedAction(txn, getDefaultSession(BOTH_JOINED));
}
@Test(expected = ProtocolStateException.class)
public void testOnMemberAddedFromLocalLeft() throws Exception {
engine.onMemberAddedAction(txn, getDefaultSession(LOCAL_LEFT));
}
@Test
public void testOnMemberAddedFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR);
assertEquals(session, engine.onMemberAddedAction(txn, session));
}
// onInviteMessage
@Test
public void testOnInviteMessageFromStart() throws Exception {
PeerSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromAwaitMember() throws Exception {
PeerSession session = getDefaultSession(AWAIT_MEMBER);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromNeitherJoined() throws Exception {
PeerSession session = getDefaultSession(NEITHER_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromLocalJoined() throws Exception {
PeerSession session = getDefaultSession(LOCAL_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromBothJoined() throws Exception {
PeerSession session = getDefaultSession(BOTH_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromLocalLeft() throws Exception {
PeerSession session = getDefaultSession(LOCAL_LEFT);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onInviteMessage(txn, session, inviteMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnInviteMessageFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR);
assertEquals(session,
engine.onInviteMessage(txn, session, inviteMessage));
}
// onJoinMessage
@Test
public void testOnJoinMessageFromAwaitMember() throws Exception {
PeerSession session = getDefaultSession(AWAIT_MEMBER);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromBothJoined() throws Exception {
PeerSession session = getDefaultSession(BOTH_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromLocalLeft() throws Exception {
PeerSession session = getDefaultSession(LOCAL_LEFT);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromStartWithInvalidDependency()
throws Exception {
JoinMessage invalidJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(START);
assertNotNull(invalidJoinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidJoinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenNotSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, invalidJoinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromStart() throws Exception {
PeerSession session = getDefaultSession(START);
assertNotNull(joinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(joinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertEquals(AWAIT_MEMBER, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinMessageFromNeitherJoinedWithInvalidDependency()
throws Exception {
JoinMessage invalidJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(NEITHER_JOINED);
assertNotNull(invalidJoinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidJoinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenNotSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, invalidJoinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromNeitherJoined() throws Exception {
PeerSession session = getDefaultSession(NEITHER_JOINED);
assertNotNull(joinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(joinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
JoinMessage myJoinMessage = new JoinMessage(messageId, contactGroupId,
privateGroupId, messageTimestamp, lastRemoteMessageId);
expectSendJoinMessage(myJoinMessage, false);
expectSetPrivateGroupVisibility(SHARED);
expectRelationshipRevealed(true);
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertEquals(BOTH_JOINED, newSession.getState());
assertEquals(myJoinMessage.getId(), newSession.getLastLocalMessageId());
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(myJoinMessage.getTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinMessageFromLocalJoinedWithInvalidDependency()
throws Exception {
JoinMessage invalidJoinMessage =
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(LOCAL_JOINED);
assertNotNull(invalidJoinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidJoinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenNotSubscribedToGroup();
PeerSession newSession =
engine.onJoinMessage(txn, session, invalidJoinMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnJoinMessageFromLocalJoined() throws Exception {
PeerSession session = getDefaultSession(LOCAL_JOINED);
assertNotNull(joinMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(joinMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectSetPrivateGroupVisibility(SHARED);
expectRelationshipRevealed(false);
PeerSession newSession =
engine.onJoinMessage(txn, session, joinMessage);
assertEquals(BOTH_JOINED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnJoinMessageFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR);
assertEquals(session,
engine.onJoinMessage(txn, session, joinMessage));
}
// onLeaveMessage
@Test
public void testOnLeaveMessageFromStart() throws Exception {
PeerSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromNeitherJoined() throws Exception {
PeerSession session = getDefaultSession(NEITHER_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromLocalJoined() throws Exception {
PeerSession session = getDefaultSession(LOCAL_JOINED);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromAwaitMemberWithInvalidDependency()
throws Exception {
LeaveMessage invalidLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(AWAIT_MEMBER);
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidLeaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromAwaitMember() throws Exception {
PeerSession session = getDefaultSession(AWAIT_MEMBER);
assertNotNull(leaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(leaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertEquals(START, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveMessageFromLocalLeftWithInvalidDependency()
throws Exception {
LeaveMessage invalidLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(LOCAL_LEFT);
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidLeaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromLocalLeft() throws Exception {
PeerSession session = getDefaultSession(LOCAL_LEFT);
assertNotNull(leaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(leaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertEquals(NEITHER_JOINED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveMessageFromBothJoinedWithInvalidDependency()
throws Exception {
LeaveMessage invalidLeaveMessage =
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
privateGroupId, 0L, lastLocalMessageId);
PeerSession session = getDefaultSession(BOTH_JOINED);
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertFalse(invalidLeaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnLeaveMessageFromBothJoined() throws Exception {
PeerSession session = getDefaultSession(BOTH_JOINED);
assertNotNull(leaveMessage.getPreviousMessageId());
assertNotNull(session.getLastRemoteMessageId());
assertTrue(leaveMessage.getPreviousMessageId()
.equals(session.getLastRemoteMessageId()));
expectSetPrivateGroupVisibility(VISIBLE); // FIXME correct?
PeerSession newSession =
engine.onLeaveMessage(txn, session, leaveMessage);
assertEquals(LOCAL_JOINED, newSession.getState());
assertEquals(session.getLastLocalMessageId(),
newSession.getLastLocalMessageId());
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
assertEquals(session.getLocalTimestamp(),
newSession.getLocalTimestamp());
assertSessionConstantsUnchanged(session, newSession);
}
@Test
public void testOnLeaveMessageFromError() throws Exception {
PeerSession session = getDefaultSession(ERROR);
assertEquals(session,
engine.onLeaveMessage(txn, session, leaveMessage));
}
// onAbortMessage
@Test
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
PeerSession session = getDefaultSession(START);
expectAbortWhenSubscribedToGroup();
PeerSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
@Test
public void testOnAbortMessageWhenSubscribed() throws Exception {
PeerSession session = getDefaultSession(START);
expectAbortWhenNotSubscribedToGroup();
PeerSession newSession =
engine.onAbortMessage(txn, session, abortMessage);
assertSessionAborted(session, newSession);
}
// helper methods
private void expectRelationshipRevealed(final boolean byContact)
throws Exception {
expectGetContactId();
context.checking(new Expectations() {{
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(privateGroupManager)
.relationshipRevealed(txn, privateGroupId, author.getId(),
byContact);
}});
}
private void expectAbortWhenSubscribedToGroup() throws Exception {
expectIsSubscribedPrivateGroup();
expectSetPrivateGroupVisibility(INVISIBLE);
expectSendAbortMessage();
}
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
expectIsNotSubscribedPrivateGroup();
expectSendAbortMessage();
}
private void assertSessionAborted(PeerSession oldSession,
PeerSession newSession) throws Exception {
assertEquals(ERROR, newSession.getState());
assertSessionRecordedSentMessage(newSession);
assertSessionConstantsUnchanged(oldSession, newSession);
}
@Override
protected void assertSessionRecordedSentMessage(Session s) {
assertEquals(messageId, s.getLastLocalMessageId());
assertEquals(lastRemoteMessageId, s.getLastRemoteMessageId());
assertEquals(messageTimestamp, s.getLocalTimestamp());
// invitation timestamp is untouched for peers
}
}

View File

@@ -2,7 +2,7 @@ package org.briarproject.briar.sharing;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchGroupException;
@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.briarproject.TestUtils.assertGroupCount;
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

View File

@@ -2,7 +2,7 @@ package org.briarproject.briar.sharing;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.TestDatabaseModule;
import org.briarproject.bramble.TestDatabaseModule;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.data.BdfList;
@@ -39,8 +39,8 @@ import java.util.Collection;
import java.util.List;
import static junit.framework.Assert.assertNotNull;
import static org.briarproject.TestUtils.getRandomBytes;
import static org.briarproject.TestUtils.getRandomString;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomString;
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.briar.api.forum.ForumSharingManager.CLIENT_ID;
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;

View File

@@ -0,0 +1,343 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.ValidatorTestCase;
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.briar.api.client.SessionId;
import org.junit.Test;
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.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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class ForumSharingValidatorTest extends ValidatorTestCase {
private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
private final String forumName =
TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH);
private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH);
private final String content =
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
@Test
public void testAcceptsInvitationWithContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, content));
assertExpectedContextForInvitation(messageContext, forumName, content);
}
@Test
public void testAcceptsInvitationWithoutContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt));
assertExpectedContextForInvitation(messageContext, forumName, null);
}
@Test
public void testAcceptsAccept() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ACCEPT, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ACCEPT);
}
@Test
public void testAcceptsDecline() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_DECLINE, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_DECLINE);
}
@Test
public void testAcceptsLeave() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_LEAVE, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_LEAVE);
}
@Test
public void testAcceptsAbort() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId));
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ABORT);
}
@Test(expected = FormatException.class)
public void testRejectsNullMessageType() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of(null, sessionId));
}
@Test(expected = FormatException.class)
public void testRejectsNonLongMessageType() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group, BdfList.of("", sessionId));
}
@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));
}
@Test(expected = FormatException.class)
public void testRejectsNullSessionId() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, null));
}
@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);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSessionId() throws Exception {
byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
}
@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));
}
@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));
}
@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));
}
@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));
}
@Test(expected = FormatException.class)
public void testRejectsNullForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, null,
salt, content));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, 123,
salt, content));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortForumName() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, "",
salt, content));
}
@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);
}
@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);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId,
invalidForumName, salt, content));
}
@Test(expected = FormatException.class)
public void testRejectsNullSalt() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
null, content));
}
@Test(expected = FormatException.class)
public void testRejectsNonRawSalt() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
123, content));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortSalt() throws Exception {
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH - 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
invalidSalt, content));
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSalt() throws Exception {
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH + 1);
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
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));
}
@Test(expected = FormatException.class)
public void testRejectsNonStringContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, 123));
}
@Test
public void testAcceptsMinLengthContent() throws Exception {
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
BdfMessageContext messageContext = v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, ""));
assertExpectedContextForInvitation(messageContext, forumName, "");
}
@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);
v.validateMessage(message, group,
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
salt, 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 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());
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.sharing;
import org.briarproject.briar.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class SharingManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}