diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java index 85f383bae4372b7cfdad2e91515b4e1df94e6a1e..248602434ef9cadf46cab96105777a4630c13dad 100644 --- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java +++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java @@ -9,6 +9,7 @@ import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Metadata; import org.briarproject.api.db.NoSuchGroupException; +import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; @@ -23,8 +24,10 @@ import org.briarproject.api.sync.MessageValidator; import org.briarproject.api.sync.ValidationManager; import org.briarproject.util.ByteUtils; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; @@ -81,19 +84,48 @@ class ValidationManagerImpl implements ValidationManager, Service, dbExecutor.execute(new Runnable() { public void run() { try { - // TODO: Don't do all of this in a single DB task + Queue<MessageId> unvalidated = new LinkedList<MessageId>(); Transaction txn = db.startTransaction(); try { - for (MessageId id : db.getMessagesToValidate(txn, c)) { - byte[] raw = db.getRawMessage(txn, id); - Message m = parseMessage(id, raw); - Group g = db.getGroup(txn, m.getGroupId()); - validateMessage(m, g); - } + unvalidated.addAll(db.getMessagesToValidate(txn, c)); + txn.setComplete(); + } finally { + db.endTransaction(txn); + } + validateNextMessage(unvalidated); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); + } + + private void validateNextMessage(final Queue<MessageId> unvalidated) { + if (unvalidated.isEmpty()) return; + dbExecutor.execute(new Runnable() { + public void run() { + try { + Message m = null; + Group g = null; + Transaction txn = db.startTransaction(); + try { + MessageId id = unvalidated.poll(); + byte[] raw = db.getRawMessage(txn, id); + m = parseMessage(id, raw); + g = db.getGroup(txn, m.getGroupId()); txn.setComplete(); + } catch (NoSuchMessageException e) { + LOG.info("Message removed before validation"); + // Continue to next message + } catch (NoSuchGroupException e) { + LOG.info("Group removed before validation"); + // Continue to next message } finally { db.endTransaction(txn); } + if (m != null && g != null) validateMessage(m, g); + validateNextMessage(unvalidated); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -158,21 +190,23 @@ class ValidationManagerImpl implements ValidationManager, Service, if (e instanceof MessageAddedEvent) { // Validate the message if it wasn't created locally MessageAddedEvent m = (MessageAddedEvent) e; - if (m.getContactId() != null) loadGroup(m.getMessage()); + if (m.getContactId() != null) loadGroupAndValidate(m.getMessage()); } } - private void loadGroup(final Message m) { + private void loadGroupAndValidate(final Message m) { dbExecutor.execute(new Runnable() { public void run() { try { + Group g; Transaction txn = db.startTransaction(); try { - validateMessage(m, db.getGroup(txn, m.getGroupId())); + g = db.getGroup(txn, m.getGroupId()); txn.setComplete(); } finally { db.endTransaction(txn); } + validateMessage(m, g); } catch (NoSuchGroupException e) { LOG.info("Group removed before validation"); } catch (DbException e) { diff --git a/briar-tests/src/org/briarproject/plugins/ImmediateExecutor.java b/briar-tests/src/org/briarproject/ImmediateExecutor.java similarity index 81% rename from briar-tests/src/org/briarproject/plugins/ImmediateExecutor.java rename to briar-tests/src/org/briarproject/ImmediateExecutor.java index bac8cf1e5df75120fb1ceb1e9eefa2b1a70e314b..ce0267885452e75a4e7fcac39e729248529c269d 100644 --- a/briar-tests/src/org/briarproject/plugins/ImmediateExecutor.java +++ b/briar-tests/src/org/briarproject/ImmediateExecutor.java @@ -1,4 +1,4 @@ -package org.briarproject.plugins; +package org.briarproject; import java.util.concurrent.Executor; diff --git a/briar-tests/src/org/briarproject/plugins/file/RemovableDrivePluginTest.java b/briar-tests/src/org/briarproject/plugins/file/RemovableDrivePluginTest.java index a70d0eaff0d9eeca8ba54a5cd5252f210167e648..cd6d4c2f892fb2587d700e8f70b4f51a79094972 100644 --- a/briar-tests/src/org/briarproject/plugins/file/RemovableDrivePluginTest.java +++ b/briar-tests/src/org/briarproject/plugins/file/RemovableDrivePluginTest.java @@ -1,11 +1,11 @@ package org.briarproject.plugins.file; import org.briarproject.BriarTestCase; +import org.briarproject.ImmediateExecutor; import org.briarproject.TestUtils; import org.briarproject.api.contact.ContactId; import org.briarproject.api.plugins.TransportConnectionWriter; import org.briarproject.api.plugins.simplex.SimplexPluginCallback; -import org.briarproject.plugins.ImmediateExecutor; import org.briarproject.plugins.file.RemovableDriveMonitor.Callback; import org.jmock.Expectations; import org.jmock.Mockery; diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java index 87abf08f1de21253e3f317121bd2dcd64f3a7bed..a548539d44a7f3cf595f94bd7c218b27851b2cc3 100644 --- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java +++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java @@ -4,6 +4,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import org.briarproject.BriarTestCase; +import org.briarproject.ImmediateExecutor; import org.briarproject.TestDatabaseModule; import org.briarproject.TestSystemModule; import org.briarproject.TestUtils; @@ -43,7 +44,6 @@ import org.briarproject.event.EventModule; import org.briarproject.identity.IdentityModule; import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.messaging.MessagingModule; -import org.briarproject.plugins.ImmediateExecutor; import org.briarproject.transport.TransportModule; import org.junit.After; import org.junit.Before; diff --git a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java index 635b2b0685934562a494303500850b0900ecd98a..a62b7add1271d904c284c2af23d5313448a9a824 100644 --- a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java +++ b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java @@ -1,6 +1,7 @@ package org.briarproject.sync; import org.briarproject.BriarTestCase; +import org.briarproject.ImmediateExecutor; import org.briarproject.TestUtils; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; @@ -10,7 +11,6 @@ import org.briarproject.api.event.EventBus; import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.PacketWriter; -import org.briarproject.plugins.ImmediateExecutor; import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.Test; diff --git a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java index 4863c85c9abe5ab41d2ec08226c2a417e1e88b9c..b55bacd90694245b784f41b51f083a31c4186516 100644 --- a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java +++ b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java @@ -1,14 +1,285 @@ package org.briarproject.sync; import org.briarproject.BriarTestCase; +import org.briarproject.ImmediateExecutor; +import org.briarproject.TestUtils; +import org.briarproject.api.UniqueId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.NoSuchGroupException; +import org.briarproject.api.db.NoSuchMessageException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.MessageAddedEvent; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.sync.MessageValidator; +import org.briarproject.api.sync.ValidationManager.ValidationHook; +import org.briarproject.util.ByteUtils; +import org.jmock.Expectations; +import org.jmock.Mockery; import org.junit.Test; -import static org.junit.Assert.fail; +import java.util.Arrays; +import java.util.concurrent.Executor; public class ValidationManagerImplTest extends BriarTestCase { + private final ClientId clientId = new ClientId(TestUtils.getRandomId()); + private final MessageId messageId = new MessageId(TestUtils.getRandomId()); + private final MessageId messageId1 = new MessageId(TestUtils.getRandomId()); + private final GroupId groupId = new GroupId(TestUtils.getRandomId()); + private final byte[] descriptor = new byte[32]; + private final Group group = new Group(groupId, clientId, descriptor); + private final long timestamp = System.currentTimeMillis(); + private final byte[] raw = new byte[123]; + private final Message message = new Message(messageId, groupId, timestamp, + raw); + private final Message message1 = new Message(messageId1, groupId, timestamp, + raw); + private final Metadata metadata = new Metadata(); + private final ContactId contactId = new ContactId(234); + + public ValidationManagerImplTest() { + // Encode the messages + System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH); + ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); + } + + @Test + public void testMessagesAreValidatedAtStartup() throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Executor dbExecutor = new ImmediateExecutor(); + final Executor cryptoExecutor = new ImmediateExecutor(); + final MessageValidator validator = context.mock(MessageValidator.class); + final ValidationHook hook = context.mock(ValidationHook.class); + final Transaction txn = new Transaction(null); + final Transaction txn1 = new Transaction(null); + final Transaction txn2 = new Transaction(null); + final Transaction txn3 = new Transaction(null); + final Transaction txn4 = new Transaction(null); + context.checking(new Expectations() {{ + // Get messages to validate + oneOf(db).startTransaction(); + will(returnValue(txn)); + oneOf(db).getMessagesToValidate(txn, clientId); + will(returnValue(Arrays.asList(messageId, messageId1))); + oneOf(db).endTransaction(txn); + // Load the first raw message and group + oneOf(db).startTransaction(); + will(returnValue(txn1)); + oneOf(db).getRawMessage(txn1, messageId); + will(returnValue(raw)); + oneOf(db).getGroup(txn1, groupId); + will(returnValue(group)); + oneOf(db).endTransaction(txn1); + // Validate the first message: valid + oneOf(validator).validateMessage(message, group); + will(returnValue(metadata)); + // Store the validation result for the first message + oneOf(db).startTransaction(); + will(returnValue(txn2)); + oneOf(db).mergeMessageMetadata(txn2, messageId, metadata); + oneOf(db).setMessageValid(txn2, message, clientId, true); + oneOf(db).setMessageShared(txn2, message, true); + // Call the hook for the first message + oneOf(hook).validatingMessage(txn2, message, clientId, metadata); + oneOf(db).endTransaction(txn2); + // Load the second raw message and group + oneOf(db).startTransaction(); + will(returnValue(txn3)); + oneOf(db).getRawMessage(txn3, messageId1); + will(returnValue(raw)); + oneOf(db).getGroup(txn3, groupId); + will(returnValue(group)); + oneOf(db).endTransaction(txn3); + // Validate the second message: invalid + oneOf(validator).validateMessage(message1, group); + will(returnValue(null)); + // Store the validation result for the second message + oneOf(db).startTransaction(); + will(returnValue(txn4)); + oneOf(db).setMessageValid(txn4, message1, clientId, false); + oneOf(db).endTransaction(txn4); + }}); + + ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, + cryptoExecutor); + vm.registerMessageValidator(clientId, validator); + vm.registerValidationHook(hook); + vm.start(); + + context.assertIsSatisfied(); + } + + @Test + public void testValidationContinuesAfterNoSuchMessageException() + throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Executor dbExecutor = new ImmediateExecutor(); + final Executor cryptoExecutor = new ImmediateExecutor(); + final MessageValidator validator = context.mock(MessageValidator.class); + final ValidationHook hook = context.mock(ValidationHook.class); + final Transaction txn = new Transaction(null); + final Transaction txn1 = new Transaction(null); + final Transaction txn2 = new Transaction(null); + final Transaction txn3 = new Transaction(null); + context.checking(new Expectations() {{ + // Get messages to validate + oneOf(db).startTransaction(); + will(returnValue(txn)); + oneOf(db).getMessagesToValidate(txn, clientId); + will(returnValue(Arrays.asList(messageId, messageId1))); + oneOf(db).endTransaction(txn); + // Load the first raw message - *gasp* it's gone! + oneOf(db).startTransaction(); + will(returnValue(txn1)); + oneOf(db).getRawMessage(txn1, messageId); + will(throwException(new NoSuchMessageException())); + oneOf(db).endTransaction(txn1); + // Load the second raw message and group + oneOf(db).startTransaction(); + will(returnValue(txn2)); + oneOf(db).getRawMessage(txn2, messageId1); + will(returnValue(raw)); + oneOf(db).getGroup(txn2, groupId); + will(returnValue(group)); + oneOf(db).endTransaction(txn2); + // Validate the second message: invalid + oneOf(validator).validateMessage(message1, group); + will(returnValue(null)); + // Store the validation result for the second message + oneOf(db).startTransaction(); + will(returnValue(txn3)); + oneOf(db).setMessageValid(txn3, message1, clientId, false); + oneOf(db).endTransaction(txn3); + }}); + + ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, + cryptoExecutor); + vm.registerMessageValidator(clientId, validator); + vm.registerValidationHook(hook); + vm.start(); + + context.assertIsSatisfied(); + } + @Test - public void testUnitTestsExist() { - fail(); // FIXME: Write tests + public void testValidationContinuesAfterNoSuchGroupException() + throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Executor dbExecutor = new ImmediateExecutor(); + final Executor cryptoExecutor = new ImmediateExecutor(); + final MessageValidator validator = context.mock(MessageValidator.class); + final ValidationHook hook = context.mock(ValidationHook.class); + final Transaction txn = new Transaction(null); + final Transaction txn1 = new Transaction(null); + final Transaction txn2 = new Transaction(null); + final Transaction txn3 = new Transaction(null); + context.checking(new Expectations() {{ + // Get messages to validate + oneOf(db).startTransaction(); + will(returnValue(txn)); + oneOf(db).getMessagesToValidate(txn, clientId); + will(returnValue(Arrays.asList(messageId, messageId1))); + oneOf(db).endTransaction(txn); + // Load the first raw message + oneOf(db).startTransaction(); + will(returnValue(txn1)); + oneOf(db).getRawMessage(txn1, messageId); + will(returnValue(raw)); + // Load the group - *gasp* it's gone! + oneOf(db).getGroup(txn1, groupId); + will(throwException(new NoSuchGroupException())); + oneOf(db).endTransaction(txn1); + // Load the second raw message and group + oneOf(db).startTransaction(); + will(returnValue(txn2)); + oneOf(db).getRawMessage(txn2, messageId1); + will(returnValue(raw)); + oneOf(db).getGroup(txn2, groupId); + will(returnValue(group)); + oneOf(db).endTransaction(txn2); + // Validate the second message: invalid + oneOf(validator).validateMessage(message1, group); + will(returnValue(null)); + // Store the validation result for the second message + oneOf(db).startTransaction(); + will(returnValue(txn3)); + oneOf(db).setMessageValid(txn3, message1, clientId, false); + oneOf(db).endTransaction(txn3); + }}); + + ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, + cryptoExecutor); + vm.registerMessageValidator(clientId, validator); + vm.registerValidationHook(hook); + vm.start(); + + context.assertIsSatisfied(); + } + + @Test + public void testNonLocalMessagesAreValidatedWhenAdded() throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Executor dbExecutor = new ImmediateExecutor(); + final Executor cryptoExecutor = new ImmediateExecutor(); + final MessageValidator validator = context.mock(MessageValidator.class); + final ValidationHook hook = context.mock(ValidationHook.class); + final Transaction txn = new Transaction(null); + final Transaction txn1 = new Transaction(null); + context.checking(new Expectations() {{ + // Load the group + oneOf(db).startTransaction(); + will(returnValue(txn)); + oneOf(db).getGroup(txn, groupId); + will(returnValue(group)); + oneOf(db).endTransaction(txn); + // Validate the message: valid + oneOf(validator).validateMessage(message, group); + will(returnValue(metadata)); + // Store the validation result + oneOf(db).startTransaction(); + will(returnValue(txn1)); + oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); + oneOf(db).setMessageValid(txn1, message, clientId, true); + oneOf(db).setMessageShared(txn1, message, true); + // Call the hook + oneOf(hook).validatingMessage(txn1, message, clientId, metadata); + oneOf(db).endTransaction(txn1); + }}); + + ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, + cryptoExecutor); + vm.registerMessageValidator(clientId, validator); + vm.registerValidationHook(hook); + vm.eventOccurred(new MessageAddedEvent(message, contactId)); + + context.assertIsSatisfied(); + } + + @Test + public void testLocalMessagesAreNotValidatedWhenAdded() throws Exception { + Mockery context = new Mockery(); + final DatabaseComponent db = context.mock(DatabaseComponent.class); + final Executor dbExecutor = new ImmediateExecutor(); + final Executor cryptoExecutor = new ImmediateExecutor(); + final MessageValidator validator = context.mock(MessageValidator.class); + final ValidationHook hook = context.mock(ValidationHook.class); + + ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, + cryptoExecutor); + vm.registerMessageValidator(clientId, validator); + vm.registerValidationHook(hook); + vm.eventOccurred(new MessageAddedEvent(message, null)); + + context.assertIsSatisfied(); } }