diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java index 87cfec0977269ba321f496c532fa72d616a56e7a..b4a9c78db6b7cea9caee363f37ef01a63fd5d151 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java @@ -18,7 +18,7 @@ import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.ForumInvitationResponseReceivedEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.forum.ForumManager; @@ -60,6 +60,7 @@ import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -669,14 +670,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase { public volatile boolean responseReceived = false; public void eventOccurred(Event e) { - if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent event = (MessageValidatedEvent) e; - if (event.getClientId() + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (event.getState() == DELIVERED && event.getClientId() .equals(forumSharingManager0.getClientId()) && !event.isLocal()) { LOG.info("TEST: Sharer received message in group " + - ((MessageValidatedEvent) e).getMessage() - .getGroupId().hashCode()); + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); } } else if (e instanceof ForumInvitationResponseReceivedEvent) { @@ -722,14 +722,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase { } public void eventOccurred(Event e) { - if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent event = (MessageValidatedEvent) e; - if (event.getClientId() + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (event.getState() == DELIVERED && event.getClientId() .equals(forumSharingManager1.getClientId()) && !event.isLocal()) { LOG.info("TEST: Invitee received message in group " + - ((MessageValidatedEvent) e).getMessage() - .getGroupId().hashCode()); + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); } } else if (e instanceof ForumInvitationReceivedEvent) { diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java index 259be1a9d4f5295d7ab36be98822125757cae24e..043fcae7d5774ddc218b3fef7ea772c207938236 100644 --- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java @@ -19,7 +19,7 @@ import org.briarproject.api.event.IntroductionAbortedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.IntroductionSucceededEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; @@ -70,6 +70,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; import static org.briarproject.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -1047,15 +1048,14 @@ public class IntroductionIntegrationTest extends BriarTestCase { @Override public void eventOccurred(Event e) { - if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent event = (MessageValidatedEvent) e; - if (event.getClientId() + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (event.getState() == DELIVERED && event.getClientId() .equals(introductionManager0.getClientId()) && !event.isLocal()) { LOG.info("TEST: Introducee" + introducee + " received message in group " + - ((MessageValidatedEvent) e).getMessage() - .getGroupId().hashCode()); + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); } } else if (e instanceof IntroductionRequestReceivedEvent) { @@ -1114,14 +1114,13 @@ public class IntroductionIntegrationTest extends BriarTestCase { @Override public void eventOccurred(Event e) { - if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent event = (MessageValidatedEvent) e; - if (event.getClientId() + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent event = (MessageStateChangedEvent) e; + if (event.getState() == DELIVERED && event.getClientId() .equals(introductionManager0.getClientId()) && !event.isLocal()) { LOG.info("TEST: Introducer received message in group " + - ((MessageValidatedEvent) e).getMessage() - .getGroupId().hashCode()); + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); } } else if (e instanceof IntroductionResponseReceivedEvent) { diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java index 166c8e84fb05ba68632d9d2921fe321bdc43fbf6..7e3d83c9df06f3710fdb934af8c1a44502bba1f9 100644 --- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java +++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java @@ -24,7 +24,7 @@ import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.IntroductionSucceededEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.lifecycle.Service; @@ -59,6 +59,7 @@ import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; import static java.util.logging.Level.WARNING; import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; class AndroidNotificationManagerImpl implements AndroidNotificationManager, Service, EventListener { @@ -156,9 +157,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, if (e instanceof SettingsUpdatedEvent) { SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; if (s.getNamespace().equals(SETTINGS_NAMESPACE)) loadSettings(); - } else if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent m = (MessageValidatedEvent) e; - if (m.isValid() && !m.isLocal()) { + } else if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; + if (!m.isLocal() && m.getState() == DELIVERED) { ClientId c = m.getClientId(); if (c.equals(messagingManager.getClientId())) showPrivateMessageNotification(m.getMessage().getGroupId()); diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index d70f7d439cc2dd2facbcd91f45daeb8d71605c3d..7b21f28ac2950306b06673f50b64f200bd766971 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -30,7 +30,7 @@ import org.briarproject.api.event.ContactStatusChangedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.identity.IdentityManager; @@ -54,6 +54,7 @@ import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAn import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.android.BriarActivity.GROUP_ID; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; public class ContactListFragment extends BaseFragment implements EventListener { @@ -230,12 +231,13 @@ public class ContactListFragment extends BaseFragment implements EventListener { } else if (e instanceof ContactRemovedEvent) { LOG.info("Contact removed"); removeItem(((ContactRemovedEvent) e).getContactId()); - } else if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent m = (MessageValidatedEvent) e; + } else if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; ClientId c = m.getClientId(); - if (m.isValid() && (c.equals(messagingManager.getClientId()) || - c.equals(introductionManager.getClientId()) || - c.equals(forumSharingManager.getClientId()))) { + if (m.getState() == DELIVERED && + (c.equals(messagingManager.getClientId()) || + c.equals(introductionManager.getClientId()) || + c.equals(forumSharingManager.getClientId()))) { LOG.info("Message added, reloading"); reloadConversation(m.getMessage().getGroupId()); } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 2c8e3b5df4eda5a4c2d4d453f7da53b8904ae3be..f91d0644051705fffc3c8ce9e05f92de695e5d0a 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -47,7 +47,7 @@ import org.briarproject.api.event.EventListener; import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.forum.ForumInvitationMessage; @@ -87,6 +87,7 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.android.contact.ConversationItem.IncomingItem; import static org.briarproject.android.contact.ConversationItem.OutgoingItem; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; public class ConversationActivity extends BriarActivity implements EventListener, OnClickListener, @@ -468,9 +469,10 @@ public class ConversationActivity extends BriarActivity LOG.info("Contact removed"); finishOnUiThread(); } - } else if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent m = (MessageValidatedEvent) e; - if (m.isValid() && m.getMessage().getGroupId().equals(groupId)) { + } else if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; + if (m.getState() == DELIVERED && + m.getMessage().getGroupId().equals(groupId)) { LOG.info("Message added, reloading"); // Mark new incoming messages as read directly if (m.isLocal()) loadMessages(); diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index b2069a6f2d7d94bf04e1b234d9c2049ced398fbc..cfa26e8c8adf0b7e0fd68a6ec1ed4cbb4a185eba 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -30,7 +30,7 @@ import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.GroupRemovedEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumPostHeader; @@ -62,6 +62,7 @@ import static java.util.logging.Level.WARNING; import static org.briarproject.android.forum.ReadForumPostActivity.RESULT_PREV_NEXT; import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH; import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; public class ForumActivity extends BriarActivity implements EventListener, OnItemClickListener { @@ -356,9 +357,10 @@ public class ForumActivity extends BriarActivity implements EventListener, } public void eventOccurred(Event e) { - if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent m = (MessageValidatedEvent) e; - if (m.isValid() && m.getMessage().getGroupId().equals(groupId)) { + if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; + if (m.getState() == DELIVERED && + m.getMessage().getGroupId().equals(groupId)) { LOG.info("Message added, reloading"); loadHeaders(); } diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index e6f0f6a26685e7feee22c25e8be0a15a322dbe2a..9c32079838aa9290bf4c9b19412fc9b67affbcd9 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -25,7 +25,7 @@ import org.briarproject.api.event.Event; import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.GroupAddedEvent; import org.briarproject.api.event.GroupRemovedEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumPostHeader; @@ -42,6 +42,7 @@ import javax.inject.Inject; import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; public class ForumListFragment extends BaseEventFragment implements View.OnClickListener { @@ -224,14 +225,13 @@ public class ForumListFragment extends BaseEventFragment implements LOG.info("Forum removed, removing from list"); removeForum(g.getGroup().getId()); } - } else if (e instanceof MessageValidatedEvent) { - MessageValidatedEvent m = (MessageValidatedEvent) e; - if (m.isValid()) { - ClientId c = m.getClientId(); - if (c.equals(forumManager.getClientId())) { - LOG.info("Forum post added, reloading"); - loadForumHeaders(m.getMessage().getGroupId()); - } + } else if (e instanceof MessageStateChangedEvent) { + MessageStateChangedEvent m = (MessageStateChangedEvent) e; + ClientId c = m.getClientId(); + if (m.getState() == DELIVERED && + c.equals(forumManager.getClientId())) { + LOG.info("Forum post added, reloading"); + loadForumHeaders(m.getMessage().getGroupId()); } } else if (e instanceof ForumInvitationReceivedEvent) { loadAvailableForums(); diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java index e145d9e8442fd022e6ee1424429950231a658cf0..123d8e4b16ae98100ca856778314eacfdc4c98de 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java @@ -22,6 +22,8 @@ import org.briarproject.api.transport.TransportKeys; import java.util.Collection; import java.util.Map; +import static org.briarproject.api.sync.ValidationManager.State; + /** * Encapsulates the database implementation and exposes high-level operations * to other components. @@ -233,6 +235,24 @@ public interface DatabaseComponent { Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c) throws DbException; + /** + * Returns the IDs of any messages that need to be delivered to the given + * client. + * <p/> + * Read-only. + */ + Collection<MessageId> getMessagesToDeliver(Transaction txn, ClientId c) + throws DbException; + + /** + * Returns the IDs of any messages that are still pending due to + * dependencies to other messages for the given client. + * <p/> + * Read-only. + */ + Collection<MessageId> getPendingMessages(Transaction txn, ClientId c) + throws DbException; + /** * Returns the message with the given ID, in serialised form, or null if * the message has been deleted. @@ -276,6 +296,22 @@ public interface DatabaseComponent { Collection<MessageStatus> getMessageStatus(Transaction txn, ContactId c, GroupId g) throws DbException; + /** + * Returns the dependencies of the given message. + * <p/> + * Read-only. + */ + Map<MessageId, State> getMessageDependencies(Transaction txn, MessageId m) + throws DbException; + + /** + * Returns all IDs of messages that depend on the given message. + * <p/> + * Read-only. + */ + Map<MessageId, State> getMessageDependents(Transaction txn, MessageId m) + throws DbException; + /** * Returns the status of the given message with respect to the given * contact. @@ -391,11 +427,17 @@ public interface DatabaseComponent { throws DbException; /** - * Marks the given message as valid or invalid. + * Sets the state of the message with respect to validation and delivery. */ - void setMessageValid(Transaction txn, Message m, ClientId c, boolean valid) + void setMessageState(Transaction txn, Message m, ClientId c, State valid) throws DbException; + /** + * Adds dependencies for a message + */ + void addMessageDependencies(Transaction txn, Message dependent, + Collection<MessageId> dependencies) throws DbException; + /** * Sets the reordering window for the given contact and transport in the * given rotation period. diff --git a/briar-api/src/org/briarproject/api/event/MessageStateChangedEvent.java b/briar-api/src/org/briarproject/api/event/MessageStateChangedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..c2aafa0347525bb9160aa064e3ea8dc9a56d7e6a --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/MessageStateChangedEvent.java @@ -0,0 +1,42 @@ +package org.briarproject.api.event; + +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Message; +import org.briarproject.api.sync.ValidationManager; +import static org.briarproject.api.sync.ValidationManager.State; + +/** + * An event that is broadcast when a message state changed. + */ +public class MessageStateChangedEvent extends Event { + + private final Message message; + private final ClientId clientId; + private final boolean local; + private final State state; + + public MessageStateChangedEvent(Message message, ClientId clientId, + boolean local, State state) { + this.message = message; + this.clientId = clientId; + this.local = local; + this.state = state; + } + + public Message getMessage() { + return message; + } + + public ClientId getClientId() { + return clientId; + } + + public boolean isLocal() { + return local; + } + + public State getState() { + return state; + } + +} diff --git a/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java b/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java deleted file mode 100644 index 26216a873019ec9c6a1fcb5407fe06e718dd913f..0000000000000000000000000000000000000000 --- a/briar-api/src/org/briarproject/api/event/MessageValidatedEvent.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.briarproject.api.event; - -import org.briarproject.api.db.Metadata; -import org.briarproject.api.sync.ClientId; -import org.briarproject.api.sync.Message; - -/** - * An event that is broadcast when a message has passed or failed validation. - */ -public class MessageValidatedEvent extends Event { - - private final Message message; - private final ClientId clientId; - private final boolean local, valid; - - public MessageValidatedEvent(Message message, ClientId clientId, - boolean local, boolean valid) { - this.message = message; - this.clientId = clientId; - this.local = local; - this.valid = valid; - } - - public Message getMessage() { - return message; - } - - public ClientId getClientId() { - return clientId; - } - - public boolean isLocal() { - return local; - } - - public boolean isValid() { - return valid; - } -} diff --git a/briar-api/src/org/briarproject/api/sync/ValidationManager.java b/briar-api/src/org/briarproject/api/sync/ValidationManager.java index 7304b2e038254426500eb2110889ec47f5a0bb5b..e92a6a6827f1fa4dc92df217a7863c1cd3225665 100644 --- a/briar-api/src/org/briarproject/api/sync/ValidationManager.java +++ b/briar-api/src/org/briarproject/api/sync/ValidationManager.java @@ -10,13 +10,13 @@ import org.briarproject.api.db.Transaction; */ public interface ValidationManager { - enum Validity { + enum State { - UNKNOWN(0), INVALID(1), VALID(2); + UNKNOWN(0), INVALID(1), PENDING(2), VALID(3), DELIVERED(4); private final int value; - Validity(int value) { + State(int value) { this.value = value; } @@ -24,8 +24,8 @@ public interface ValidationManager { return value; } - public static Validity fromValue(int value) { - for (Validity s : values()) if (s.value == value) return s; + public static State fromValue(int value) { + for (State s : values()) if (s.value == value) return s; throw new IllegalArgumentException(); } } diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index 6589fbc81477147535e4af61402ae8944b2d8963..2fbd55f4ac8523571f361b3e8df105691a90fe56 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -16,7 +16,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; -import org.briarproject.api.sync.ValidationManager.Validity; +import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.transport.TransportKeys; import java.util.Collection; @@ -79,7 +79,7 @@ interface Database<T> { /** * Stores a message. */ - void addMessage(T txn, Message m, Validity validity, boolean shared) + void addMessage(T txn, Message m, State state, boolean shared) throws DbException; /** @@ -326,6 +326,24 @@ interface Database<T> { MessageStatus getMessageStatus(T txn, ContactId c, MessageId m) throws DbException; + /** + * Returns the dependencies of the given message. + * This method makes sure that dependencies in different groups + * are returned as {@link ValidationManager.State.INVALID}. + * <p/> + * Read-only. + */ + Map<MessageId, State> getMessageDependencies(T txn, MessageId m) + throws DbException; + + /** + * Returns all IDs of messages that depend on the given message. + * <p/> + * Read-only. + */ + Map<MessageId, State> getMessageDependents(T txn, MessageId m) + throws DbException; + /** * Returns the IDs of some messages received from the given contact that * need to be acknowledged, up to the given number of messages. @@ -371,6 +389,24 @@ interface Database<T> { Collection<MessageId> getMessagesToValidate(T txn, ClientId c) throws DbException; + /** + * Returns the IDs of any messages that need to be delivered to the given + * client. + * <p/> + * Read-only. + */ + Collection<MessageId> getMessagesToDeliver(T txn, ClientId c) + throws DbException; + + /** + * Returns the IDs of any messages that are still pending due to + * dependencies to other messages for the given client. + * <p/> + * Read-only. + */ + Collection<MessageId> getPendingMessages(T txn, ClientId c) + throws DbException; + /** * Returns the message with the given ID, in serialised form, or null if * the message has been deleted. @@ -538,7 +574,14 @@ interface Database<T> { /** * Marks the given message as valid or invalid. */ - void setMessageValid(T txn, MessageId m, boolean valid) throws DbException; + void setMessageState(T txn, MessageId m, State state) + throws DbException; + + /* + * Adds a dependency between two MessageIds + */ + void addMessageDependency(T txn, MessageId dependentId, + MessageId dependencyId) throws DbException; /** * Sets the reordering window for the given contact and transport in the diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 37c1f08849656f65599113db5264d502f1171928..4f84a04bd047e0a1054743cd7331a8e90ab7c3a0 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -29,8 +29,8 @@ import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; -import org.briarproject.api.event.MessageValidatedEvent; import org.briarproject.api.event.MessagesAckedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.identity.Author; @@ -47,7 +47,7 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; import org.briarproject.api.sync.Offer; import org.briarproject.api.sync.Request; -import org.briarproject.api.sync.ValidationManager.Validity; +import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.transport.TransportKeys; import java.util.ArrayList; @@ -64,8 +64,8 @@ import java.util.logging.Logger; import javax.inject.Inject; import static java.util.logging.Level.WARNING; -import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; -import static org.briarproject.api.sync.ValidationManager.Validity.VALID; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; +import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; class DatabaseComponentImpl<T> implements DatabaseComponent { @@ -193,17 +193,18 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { if (!db.containsGroup(txn, m.getGroupId())) throw new NoSuchGroupException(); if (!db.containsMessage(txn, m.getId())) { - addMessage(txn, m, VALID, shared); + addMessage(txn, m, DELIVERED, shared); transaction.attach(new MessageAddedEvent(m, null)); - transaction.attach(new MessageValidatedEvent(m, c, true, true)); + transaction.attach(new MessageStateChangedEvent(m, c, true, + DELIVERED)); if (shared) transaction.attach(new MessageSharedEvent(m)); } db.mergeMessageMetadata(txn, m.getId(), meta); } - private void addMessage(T txn, Message m, Validity validity, boolean shared) + private void addMessage(T txn, Message m, State state, boolean shared) throws DbException { - db.addMessage(txn, m, validity, shared); + db.addMessage(txn, m, state, shared); for (ContactId c : db.getVisibility(txn, m.getGroupId())) { boolean offered = db.removeOfferedMessage(txn, c, m.getId()); db.addStatus(txn, c, m.getId(), offered, offered); @@ -411,6 +412,18 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return db.getMessagesToValidate(txn, c); } + public Collection<MessageId> getMessagesToDeliver(Transaction transaction, + ClientId c) throws DbException { + T txn = unbox(transaction); + return db.getMessagesToDeliver(txn, c); + } + + public Collection<MessageId> getPendingMessages(Transaction transaction, + ClientId c) throws DbException { + T txn = unbox(transaction); + return db.getPendingMessages(txn, c); + } + public byte[] getRawMessage(Transaction transaction, MessageId m) throws DbException { T txn = unbox(transaction); @@ -463,6 +476,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { return db.getMessageStatus(txn, c, m); } + public Map<MessageId, State> getMessageDependencies(Transaction transaction, + MessageId m) throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + return db.getMessageDependencies(txn, m); + } + + public Map<MessageId, State> getMessageDependents(Transaction transaction, + MessageId m) throws DbException { + T txn = unbox(transaction); + if (!db.containsMessage(txn, m)) + throw new NoSuchMessageException(); + return db.getMessageDependents(txn, m); + } + public Settings getSettings(Transaction transaction, String namespace) throws DbException { T txn = unbox(transaction); @@ -664,14 +693,26 @@ class DatabaseComponentImpl<T> implements DatabaseComponent { if (shared) transaction.attach(new MessageSharedEvent(m)); } - public void setMessageValid(Transaction transaction, Message m, ClientId c, - boolean valid) throws DbException { + public void setMessageState(Transaction transaction, Message m, ClientId c, + State state) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsMessage(txn, m.getId())) throw new NoSuchMessageException(); - db.setMessageValid(txn, m.getId(), valid); - transaction.attach(new MessageValidatedEvent(m, c, false, valid)); + db.setMessageState(txn, m.getId(), state); + transaction.attach(new MessageStateChangedEvent(m, c, false, state)); + } + + public void addMessageDependencies(Transaction transaction, + Message dependent, Collection<MessageId> dependencies) + throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsMessage(txn, dependent.getId())) + throw new NoSuchMessageException(); + for (MessageId dependencyId : dependencies) { + db.addMessageDependency(txn, dependent.getId(), dependencyId); + } } public void setReorderingWindow(Transaction transaction, ContactId c, diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index e3e026f221b757029d6542927878bc8ed52e213c..a52fbe2a1da6c9729518c91fbb7e61cd5f39ddca 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -19,7 +19,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; -import org.briarproject.api.sync.ValidationManager.Validity; +import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.system.Clock; import org.briarproject.api.transport.IncomingKeys; import org.briarproject.api.transport.OutgoingKeys; @@ -49,9 +49,11 @@ import java.util.logging.Logger; import static java.util.logging.Level.WARNING; import static org.briarproject.api.db.Metadata.REMOVE; -import static org.briarproject.api.sync.ValidationManager.Validity.INVALID; -import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; -import static org.briarproject.api.sync.ValidationManager.Validity.VALID; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; +import static org.briarproject.api.sync.ValidationManager.State.INVALID; +import static org.briarproject.api.sync.ValidationManager.State.PENDING; +import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.State.VALID; import static org.briarproject.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY; import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE; @@ -65,8 +67,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry; */ abstract class JdbcDatabase implements Database<Connection> { - private static final int SCHEMA_VERSION = 24; - private static final int MIN_SCHEMA_VERSION = 24; + private static final int SCHEMA_VERSION = 25; + private static final int MIN_SCHEMA_VERSION = 25; private static final String CREATE_SETTINGS = "CREATE TABLE settings" @@ -131,7 +133,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " (messageId HASH NOT NULL," + " groupId HASH NOT NULL," + " timestamp BIGINT NOT NULL," - + " valid INT NOT NULL," + + " state INT NOT NULL," + " shared BOOLEAN NOT NULL," + " length INT NOT NULL," + " raw BLOB," // Null if message has been deleted @@ -150,6 +152,14 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES messages (messageId)" + " ON DELETE CASCADE)"; + private static final String CREATE_MESSAGE_DEPENDENCIES = + "CREATE TABLE messageDependencies" + + " (messageId HASH NOT NULL," + + " dependencyId HASH NOT NULL," // Not a foreign key + + " FOREIGN KEY (messageId)" + + " REFERENCES messages (messageId)" + + " ON DELETE CASCADE)"; + private static final String CREATE_OFFERS = "CREATE TABLE offers" + " (messageId HASH NOT NULL," // Not a foreign key @@ -320,6 +330,7 @@ abstract class JdbcDatabase implements Database<Connection> { s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES)); s.executeUpdate(insertTypeNames(CREATE_MESSAGES)); s.executeUpdate(insertTypeNames(CREATE_MESSAGE_METADATA)); + s.executeUpdate(insertTypeNames(CREATE_MESSAGE_DEPENDENCIES)); s.executeUpdate(insertTypeNames(CREATE_OFFERS)); s.executeUpdate(insertTypeNames(CREATE_STATUSES)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS)); @@ -520,18 +531,18 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void addMessage(Connection txn, Message m, Validity validity, + public void addMessage(Connection txn, Message m, State state, boolean shared) throws DbException { PreparedStatement ps = null; try { String sql = "INSERT INTO messages (messageId, groupId, timestamp," - + " valid, shared, length, raw)" + + " state, shared, length, raw)" + " VALUES (?, ?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setBytes(1, m.getId().getBytes()); ps.setBytes(2, m.getGroupId().getBytes()); ps.setLong(3, m.getTimestamp()); - ps.setInt(4, validity.getValue()); + ps.setInt(4, state.getValue()); ps.setBoolean(5, shared); byte[] raw = m.getRaw(); ps.setInt(6, raw.length); @@ -596,6 +607,25 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void addMessageDependency(Connection txn, MessageId dependentId, + MessageId dependencyId) throws DbException { + PreparedStatement ps = null; + try { + String sql = + "INSERT INTO messageDependencies (messageId, dependencyId)" + + " VALUES (?, ?)"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, dependentId.getBytes()); + ps.setBytes(2, dependencyId.getBytes()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + public void addTransport(Connection txn, TransportId t, int maxLatency) throws DbException { PreparedStatement ps = null; @@ -1135,10 +1165,33 @@ abstract class JdbcDatabase implements Database<Connection> { } } + private Collection<MessageId> getMessageIds(Connection txn, GroupId g, + State state) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT messageId FROM messages" + + " WHERE state = ? AND groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, state.getValue()); + ps.setBytes(2, g.getBytes()); + rs = ps.executeQuery(); + List<MessageId> ids = new ArrayList<MessageId>(); + while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); + rs.close(); + ps.close(); + return Collections.unmodifiableList(ids); + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<MessageId> getMessageIds(Connection txn, GroupId g, Metadata query) throws DbException { - // If there are no query terms, return all messages - if (query.isEmpty()) return getMessageIds(txn, g); + // If there are no query terms, return all delivered messages + if (query.isEmpty()) return getMessageIds(txn, g, DELIVERED); PreparedStatement ps = null; ResultSet rs = null; try { @@ -1148,12 +1201,14 @@ abstract class JdbcDatabase implements Database<Connection> { + " FROM messages AS m" + " JOIN messageMetadata AS md" + " ON m.messageId = md.messageId" - + " WHERE groupId = ? AND key = ? AND value = ?"; + + " WHERE state = ? AND groupId = ?" + + " AND key = ? AND value = ?"; for (Entry<String, byte[]> e : query.entrySet()) { ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); - ps.setString(2, e.getKey()); - ps.setBytes(3, e.getValue()); + ps.setInt(1, DELIVERED.getValue()); + ps.setBytes(2, g.getBytes()); + ps.setString(3, e.getKey()); + ps.setBytes(4, e.getValue()); rs = ps.executeQuery(); Set<MessageId> ids = new HashSet<MessageId>(); while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); @@ -1182,10 +1237,11 @@ abstract class JdbcDatabase implements Database<Connection> { + " FROM messages AS m" + " JOIN messageMetadata AS md" + " ON m.messageId = md.messageId" - + " WHERE groupId = ?" + + " WHERE state = ? AND groupId = ?" + " ORDER BY m.messageId"; ps = txn.prepareStatement(sql); - ps.setBytes(1, g.getBytes()); + ps.setInt(1, DELIVERED.getValue()); + ps.setBytes(2, g.getBytes()); rs = ps.executeQuery(); Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>(); Metadata metadata = null; @@ -1223,23 +1279,38 @@ abstract class JdbcDatabase implements Database<Connection> { public Metadata getGroupMetadata(Connection txn, GroupId g) throws DbException { - return getMetadata(txn, g.getBytes(), "groupMetadata", "groupId"); + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT key, value FROM groupMetadata" + + " WHERE groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, g.getBytes()); + rs = ps.executeQuery(); + Metadata metadata = new Metadata(); + while (rs.next()) metadata.put(rs.getString(1), rs.getBytes(2)); + rs.close(); + ps.close(); + return metadata; + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } } public Metadata getMessageMetadata(Connection txn, MessageId m) throws DbException { - return getMetadata(txn, m.getBytes(), "messageMetadata", "messageId"); - } - - private Metadata getMetadata(Connection txn, byte[] id, String tableName, - String columnName) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = "SELECT key, value FROM " + tableName - + " WHERE " + columnName + " = ?"; + String sql = "SELECT key, value FROM messageMetadata AS md" + + " JOIN messages AS m" + + " ON m.messageId = md.messageId" + + " WHERE m.state = ? AND md.messageId = ?"; ps = txn.prepareStatement(sql); - ps.setBytes(1, id); + ps.setInt(1, DELIVERED.getValue()); + ps.setBytes(2, m.getBytes()); rs = ps.executeQuery(); Metadata metadata = new Metadata(); while (rs.next()) metadata.put(rs.getString(1), rs.getBytes(2)); @@ -1312,6 +1383,91 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Map<MessageId, State> getMessageDependencies(Connection txn, + MessageId m) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT d.dependencyId, m.state, m.groupId" + + " FROM messageDependencies AS d" + + " LEFT OUTER JOIN messages AS m" + + " ON d.dependencyId = m.messageId" + + " WHERE d.messageId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, m.getBytes()); + rs = ps.executeQuery(); + Map<MessageId, State> dependencies = new HashMap<MessageId, State>(); + while (rs.next()) { + MessageId messageId = new MessageId(rs.getBytes(1)); + State state = State.fromValue(rs.getInt(2)); + if (state != UNKNOWN) { + // set dependency invalid if it is in a different group + if (!hasGroupId(txn, m, rs.getBytes(3))) state = INVALID; + } + dependencies.put(messageId, state); + } + rs.close(); + ps.close(); + return Collections.unmodifiableMap(dependencies); + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + private boolean hasGroupId(Connection txn, MessageId m, byte[] g) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM messages" + + " WHERE messageId = ? AND groupId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, m.getBytes()); + ps.setBytes(2, g); + rs = ps.executeQuery(); + boolean same = rs.next(); + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return same; + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + + public Map<MessageId, State> getMessageDependents(Connection txn, + MessageId m) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT d.messageId, m.state" + + " FROM messageDependencies AS d" + + " LEFT OUTER JOIN messages AS m" + + " ON d.messageId = m.messageId" + + " WHERE dependencyId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, m.getBytes()); + rs = ps.executeQuery(); + Map<MessageId, State> dependents = new HashMap<MessageId, State>(); + while (rs.next()) { + MessageId messageId = new MessageId(rs.getBytes(1)); + State state = State.fromValue(rs.getInt(2)); + dependents.put(messageId, state); + } + rs.close(); + ps.close(); + return Collections.unmodifiableMap(dependents); + } catch (SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c, int maxMessages) throws DbException { PreparedStatement ps = null; @@ -1346,13 +1502,13 @@ abstract class JdbcDatabase implements Database<Connection> { + " JOIN statuses AS s" + " ON m.messageId = s.messageId" + " WHERE contactId = ?" - + " AND valid = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE AND requested = FALSE" + " AND expiry < ?" + " ORDER BY timestamp DESC LIMIT ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, VALID.getValue()); + ps.setInt(2, DELIVERED.getValue()); ps.setLong(3, now); ps.setInt(4, maxMessages); rs = ps.executeQuery(); @@ -1402,13 +1558,13 @@ abstract class JdbcDatabase implements Database<Connection> { + " JOIN statuses AS s" + " ON m.messageId = s.messageId" + " WHERE contactId = ?" - + " AND valid = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE" + " AND expiry < ?" + " ORDER BY timestamp DESC"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, VALID.getValue()); + ps.setInt(2, DELIVERED.getValue()); ps.setLong(3, now); rs = ps.executeQuery(); List<MessageId> ids = new ArrayList<MessageId>(); @@ -1431,14 +1587,29 @@ abstract class JdbcDatabase implements Database<Connection> { public Collection<MessageId> getMessagesToValidate(Connection txn, ClientId c) throws DbException { + return getMessagesInState(txn, c, UNKNOWN); + } + + public Collection<MessageId> getMessagesToDeliver(Connection txn, + ClientId c) throws DbException { + return getMessagesInState(txn, c, VALID); + } + + public Collection<MessageId> getPendingMessages(Connection txn, + ClientId c) throws DbException { + return getMessagesInState(txn, c, PENDING); + } + + private Collection<MessageId> getMessagesInState(Connection txn, ClientId c, + State state) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { String sql = "SELECT messageId FROM messages AS m" + " JOIN groups AS g ON m.groupId = g.groupId" - + " WHERE valid = ? AND clientId = ? AND raw IS NOT NULL"; + + " WHERE state = ? AND clientId = ? AND raw IS NOT NULL"; ps = txn.prepareStatement(sql); - ps.setInt(1, UNKNOWN.getValue()); + ps.setInt(1, state.getValue()); ps.setBytes(2, c.getBytes()); rs = ps.executeQuery(); List<MessageId> ids = new ArrayList<MessageId>(); @@ -1485,13 +1656,13 @@ abstract class JdbcDatabase implements Database<Connection> { + " JOIN statuses AS s" + " ON m.messageId = s.messageId" + " WHERE contactId = ?" - + " AND valid = ? AND shared = TRUE AND raw IS NOT NULL" + + " AND state = ? AND shared = TRUE AND raw IS NOT NULL" + " AND seen = FALSE AND requested = TRUE" + " AND expiry < ?" + " ORDER BY timestamp DESC"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, VALID.getValue()); + ps.setInt(2, DELIVERED.getValue()); ps.setLong(3, now); rs = ps.executeQuery(); List<MessageId> ids = new ArrayList<MessageId>(); @@ -2081,13 +2252,13 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setMessageValid(Connection txn, MessageId m, boolean valid) + public void setMessageState(Connection txn, MessageId m, State state) throws DbException { PreparedStatement ps = null; try { - String sql = "UPDATE messages SET valid = ? WHERE messageId = ?"; + String sql = "UPDATE messages SET state = ? WHERE messageId = ?"; ps = txn.prepareStatement(sql); - ps.setInt(1, valid ? VALID.getValue() : INVALID.getValue()); + ps.setInt(1, state.getValue()); ps.setBytes(2, m.getBytes()); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java index 1e85803a22aba9d836f5564b23b3fb8854f10917..487c31d52c003ec854d808f32719648e4e52f3f2 100644 --- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java +++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java @@ -36,6 +36,8 @@ import javax.inject.Inject; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.briarproject.api.sync.ValidationManager.State.INVALID; +import static org.briarproject.api.sync.ValidationManager.State.VALID; class ValidationManagerImpl implements ValidationManager, Service, EventListener { @@ -178,7 +180,7 @@ class ValidationManagerImpl implements ValidationManager, Service, try { Metadata meta = result.getMetadata(); db.mergeMessageMetadata(txn, m.getId(), meta); - db.setMessageValid(txn, m, c, true); + db.setMessageState(txn, m.getId(), c, VALID); db.setMessageShared(txn, m, true); IncomingMessageHook hook = hooks.get(c); if (hook != null) @@ -202,7 +204,7 @@ class ValidationManagerImpl implements ValidationManager, Service, try { Transaction txn = db.startTransaction(false); try { - db.setMessageValid(txn, m, c, false); + db.setMessageState(txn, m.getId(), c, INVALID); txn.setComplete(); } finally { db.endTransaction(txn); diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java index ff6f7a918cc75941551be6c222aa64bade8358c5..6a4db39e815c184f835145d8a325832f45d5831e 100644 --- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java +++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java @@ -29,7 +29,7 @@ import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; -import org.briarproject.api.event.MessageValidatedEvent; +import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.SettingsUpdatedEvent; @@ -53,6 +53,7 @@ import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -60,8 +61,9 @@ import java.util.Map; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; -import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; -import static org.briarproject.api.sync.ValidationManager.Validity.VALID; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; +import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.State.VALID; import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.junit.Assert.assertEquals; @@ -264,7 +266,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { will(returnValue(true)); oneOf(database).containsMessage(txn, messageId); will(returnValue(false)); - oneOf(database).addMessage(txn, message, VALID, true); + oneOf(database).addMessage(txn, message, DELIVERED, true); oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).getVisibility(txn, groupId); will(returnValue(Collections.singletonList(contactId))); @@ -274,7 +276,7 @@ public class DatabaseComponentImplTest extends BriarTestCase { oneOf(database).commitTransaction(txn); // The message was added, so the listeners should be called oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); - oneOf(eventBus).broadcast(with(any(MessageValidatedEvent.class))); + oneOf(eventBus).broadcast(with(any(MessageStateChangedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, @@ -675,11 +677,11 @@ public class DatabaseComponentImplTest extends BriarTestCase { final EventBus eventBus = context.mock(EventBus.class); context.checking(new Expectations() {{ // Check whether the message is in the DB (which it's not) - exactly(8).of(database).startTransaction(); + exactly(10).of(database).startTransaction(); will(returnValue(txn)); - exactly(8).of(database).containsMessage(txn, messageId); + exactly(10).of(database).containsMessage(txn, messageId); will(returnValue(false)); - exactly(8).of(database).abortTransaction(txn); + exactly(10).of(database).abortTransaction(txn); // This is needed for getMessageStatus() to proceed exactly(1).of(database).containsContact(txn, contactId); will(returnValue(true)); @@ -759,7 +761,27 @@ public class DatabaseComponentImplTest extends BriarTestCase { transaction = db.startTransaction(false); try { - db.setMessageValid(transaction, message, clientId, true); + db.setMessageState(transaction, message, clientId, VALID); + fail(); + } catch (NoSuchMessageException expected) { + // Expected + } finally { + db.endTransaction(transaction); + } + + transaction = db.startTransaction(true); + try { + db.getMessageDependencies(transaction, messageId); + fail(); + } catch (NoSuchMessageException expected) { + // Expected + } finally { + db.endTransaction(transaction); + } + + transaction = db.startTransaction(true); + try { + db.getMessageDependents(transaction, messageId); fail(); } catch (NoSuchMessageException expected) { // Expected @@ -1595,4 +1617,79 @@ public class DatabaseComponentImplTest extends BriarTestCase { context.assertIsSatisfied(); } + + @Test + @SuppressWarnings("unchecked") + public void testMessageDependencies() throws Exception { + final int shutdownHandle = 12345; + Mockery context = new Mockery(); + final Database<Object> database = context.mock(Database.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); + final EventBus eventBus = context.mock(EventBus.class); + final MessageId messageId2 = new MessageId(TestUtils.getRandomId()); + context.checking(new Expectations() {{ + // open() + oneOf(database).open(); + will(returnValue(false)); + oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); + will(returnValue(shutdownHandle)); + // startTransaction() + oneOf(database).startTransaction(); + will(returnValue(txn)); + // addLocalMessage() + oneOf(database).containsGroup(txn, groupId); + will(returnValue(true)); + oneOf(database).containsMessage(txn, messageId); + will(returnValue(false)); + oneOf(database).addMessage(txn, message, DELIVERED, true); + oneOf(database).getVisibility(txn, groupId); + will(returnValue(Collections.singletonList(contactId))); + oneOf(database).mergeMessageMetadata(txn, messageId, metadata); + oneOf(database).removeOfferedMessage(txn, contactId, messageId); + will(returnValue(false)); + oneOf(database).addStatus(txn, contactId, messageId, false, false); + // addMessageDependencies() + oneOf(database).containsMessage(txn, messageId); + will(returnValue(true)); + oneOf(database).addMessageDependency(txn, messageId, messageId1); + oneOf(database).addMessageDependency(txn, messageId, messageId2); + // getMessageDependencies() + oneOf(database).containsMessage(txn, messageId); + will(returnValue(true)); + oneOf(database).getMessageDependencies(txn, messageId); + // getMessageDependents() + oneOf(database).containsMessage(txn, messageId); + will(returnValue(true)); + oneOf(database).getMessageDependents(txn, messageId); + // broadcast for message added event + oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); + oneOf(eventBus).broadcast(with(any(MessageStateChangedEvent.class))); + oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); + // endTransaction() + oneOf(database).commitTransaction(txn); + // close() + oneOf(shutdown).removeShutdownHook(shutdownHandle); + oneOf(database).close(); + }}); + DatabaseComponent db = createDatabaseComponent(database, eventBus, + shutdown); + + assertFalse(db.open()); + Transaction transaction = db.startTransaction(false); + try { + db.addLocalMessage(transaction, message, clientId, metadata, true); + Collection<MessageId> dependencies = new ArrayList<>(2); + dependencies.add(messageId1); + dependencies.add(messageId2); + db.addMessageDependencies(transaction, message, dependencies); + db.getMessageDependencies(transaction, messageId); + db.getMessageDependents(transaction, messageId); + transaction.setComplete(); + } finally { + db.endTransaction(transaction); + } + db.close(); + + context.assertIsSatisfied(); + } } diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java index d967b815973b6bb6246dc23d6075b5621522e7b7..9d5b1f0b912e9cd94d5086a31d17b546859fb1e4 100644 --- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java +++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java @@ -19,6 +19,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageStatus; +import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.transport.IncomingKeys; import org.briarproject.api.transport.OutgoingKeys; import org.briarproject.api.transport.TransportKeys; @@ -46,8 +47,11 @@ import static org.briarproject.api.db.Metadata.REMOVE; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; -import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; -import static org.briarproject.api.sync.ValidationManager.Validity.VALID; +import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; +import static org.briarproject.api.sync.ValidationManager.State.INVALID; +import static org.briarproject.api.sync.ValidationManager.State.PENDING; +import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN; +import static org.briarproject.api.sync.ValidationManager.State.VALID; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -114,7 +118,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addGroup(txn, group); assertTrue(db.containsGroup(txn, groupId)); assertFalse(db.containsMessage(txn, messageId)); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); assertTrue(db.containsMessage(txn, messageId)); db.commitTransaction(txn); db.close(); @@ -152,7 +156,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a group and a message db.addGroup(txn, group); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); // Removing the group should remove the message assertTrue(db.containsMessage(txn, messageId)); @@ -174,7 +178,7 @@ public class H2DatabaseTest extends BriarTestCase { true)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); // The message has no status yet, so it should not be sendable Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, @@ -202,7 +206,7 @@ public class H2DatabaseTest extends BriarTestCase { } @Test - public void testSendableMessagesMustBeValid() throws Exception { + public void testSendableMessagesMustBeDelivered() throws Exception { Database<Connection> db = open(false); Connection txn = db.startTransaction(); @@ -222,15 +226,31 @@ public class H2DatabaseTest extends BriarTestCase { ids = db.getMessagesToOffer(txn, contactId, 100); assertTrue(ids.isEmpty()); - // Marking the message valid should make it sendable - db.setMessageValid(txn, messageId, true); + // Marking the message delivered should make it sendable + db.setMessageState(txn, messageId, DELIVERED); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); assertEquals(Collections.singletonList(messageId), ids); ids = db.getMessagesToOffer(txn, contactId, 100); assertEquals(Collections.singletonList(messageId), ids); // Marking the message invalid should make it unsendable - db.setMessageValid(txn, messageId, false); + db.setMessageState(txn, messageId, INVALID); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + // Marking the message valid should make it unsendable + // TODO do we maybe want to already send valid messages? If we do, we need also to call db.setMessageShared() earlier. + db.setMessageState(txn, messageId, VALID); + ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); + assertTrue(ids.isEmpty()); + ids = db.getMessagesToOffer(txn, contactId, 100); + assertTrue(ids.isEmpty()); + + // Marking the message pending should make it unsendable + // TODO do we maybe want to already send pending messages? If we do, we need also to call db.setMessageShared() earlier. + db.setMessageState(txn, messageId, PENDING); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); assertTrue(ids.isEmpty()); ids = db.getMessagesToOffer(txn, contactId, 100); @@ -251,7 +271,7 @@ public class H2DatabaseTest extends BriarTestCase { true)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, VALID, false); + db.addMessage(txn, message, DELIVERED, false); db.addStatus(txn, contactId, messageId, false, false); // The message is not shared, so it should not be sendable @@ -290,7 +310,7 @@ public class H2DatabaseTest extends BriarTestCase { true)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); // The message is sendable, but too large to send @@ -321,10 +341,10 @@ public class H2DatabaseTest extends BriarTestCase { // Add some messages to ack MessageId messageId1 = new MessageId(TestUtils.getRandomId()); Message message1 = new Message(messageId1, groupId, timestamp, raw); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, true); db.raiseAckFlag(txn, contactId, messageId); - db.addMessage(txn, message1, VALID, true); + db.addMessage(txn, message1, DELIVERED, true); db.addStatus(txn, contactId, messageId1, false, true); db.raiseAckFlag(txn, contactId, messageId1); @@ -354,7 +374,7 @@ public class H2DatabaseTest extends BriarTestCase { true)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); // Retrieve the message from the database and mark it as sent @@ -396,7 +416,7 @@ public class H2DatabaseTest extends BriarTestCase { // Storing a message should reduce the free space Connection txn = db.startTransaction(); db.addGroup(txn, group); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.commitTransaction(txn); assertTrue(db.getFreeSpace() < free); @@ -555,7 +575,7 @@ public class H2DatabaseTest extends BriarTestCase { assertEquals(contactId, db.addContact(txn, author, localAuthorId, true)); db.addGroup(txn, group); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); // The group is not visible @@ -866,7 +886,7 @@ public class H2DatabaseTest extends BriarTestCase { // Add a group and a message db.addGroup(txn, group); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); // Attach some metadata to the message Metadata metadata = new Metadata(); @@ -930,6 +950,67 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testMessageMetadataOnlyForDeliveredMessages() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a group and a message + db.addGroup(txn, group); + db.addMessage(txn, message, DELIVERED, true); + + // Attach some metadata to the message + Metadata metadata = new Metadata(); + metadata.put("foo", new byte[]{'b', 'a', 'r'}); + metadata.put("baz", new byte[]{'b', 'a', 'm'}); + db.mergeMessageMetadata(txn, messageId, metadata); + + // Retrieve the metadata for the message + Metadata retrieved = db.getMessageMetadata(txn, messageId); + assertEquals(2, retrieved.size()); + assertTrue(retrieved.containsKey("foo")); + assertArrayEquals(metadata.get("foo"), retrieved.get("foo")); + assertTrue(retrieved.containsKey("baz")); + assertArrayEquals(metadata.get("baz"), retrieved.get("baz")); + Map<MessageId, Metadata> map = db.getMessageMetadata(txn, groupId); + assertEquals(1, map.size()); + assertTrue(map.get(messageId).containsKey("foo")); + assertArrayEquals(metadata.get("foo"), map.get(messageId).get("foo")); + assertTrue(map.get(messageId).containsKey("baz")); + assertArrayEquals(metadata.get("baz"), map.get(messageId).get("baz")); + + // No metadata for unknown messages + db.setMessageState(txn, messageId, UNKNOWN); + retrieved = db.getMessageMetadata(txn, messageId); + assertTrue(retrieved.isEmpty()); + map = db.getMessageMetadata(txn, groupId); + assertTrue(map.isEmpty()); + + // No metadata for invalid messages + db.setMessageState(txn, messageId, INVALID); + retrieved = db.getMessageMetadata(txn, messageId); + assertTrue(retrieved.isEmpty()); + map = db.getMessageMetadata(txn, groupId); + assertTrue(map.isEmpty()); + + // No metadata for valid messages + db.setMessageState(txn, messageId, VALID); + retrieved = db.getMessageMetadata(txn, messageId); + assertTrue(retrieved.isEmpty()); + map = db.getMessageMetadata(txn, groupId); + assertTrue(map.isEmpty()); + + // No metadata for pending messages + db.setMessageState(txn, messageId, PENDING); + retrieved = db.getMessageMetadata(txn, messageId); + assertTrue(retrieved.isEmpty()); + map = db.getMessageMetadata(txn, groupId); + assertTrue(map.isEmpty()); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testMetadataQueries() throws Exception { MessageId messageId1 = new MessageId(TestUtils.getRandomId()); @@ -940,8 +1021,8 @@ public class H2DatabaseTest extends BriarTestCase { // Add a group and two messages db.addGroup(txn, group); - db.addMessage(txn, message, VALID, true); - db.addMessage(txn, message1, VALID, true); + db.addMessage(txn, message, DELIVERED, true); + db.addMessage(txn, message1, DELIVERED, true); // Attach some metadata to the messages Metadata metadata = new Metadata(); @@ -1034,6 +1115,257 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception { + MessageId messageId1 = new MessageId(TestUtils.getRandomId()); + Message message1 = new Message(messageId1, groupId, timestamp, raw); + + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a group and two messages + db.addGroup(txn, group); + db.addMessage(txn, message, DELIVERED, true); + db.addMessage(txn, message1, DELIVERED, true); + + // Attach some metadata to the messages + Metadata metadata = new Metadata(); + metadata.put("foo", new byte[]{'b', 'a', 'r'}); + metadata.put("baz", new byte[]{'b', 'a', 'm'}); + db.mergeMessageMetadata(txn, messageId, metadata); + Metadata metadata1 = new Metadata(); + metadata1.put("foo", new byte[]{'b', 'a', 'r'}); + db.mergeMessageMetadata(txn, messageId1, metadata1); + + for (int i = 1; i <= 2; i++) { + Metadata query; + if (i == 1) { + // Query the metadata with an empty query + query = new Metadata(); + } else { + // Query for foo + query = new Metadata(); + query.put("foo", metadata.get("foo")); + } + + db.setMessageState(txn, messageId, DELIVERED); + db.setMessageState(txn, messageId1, DELIVERED); + Map<MessageId, Metadata> all = + db.getMessageMetadata(txn, groupId, query); + assertEquals(2, all.size()); + assertEquals(2, all.get(messageId).size()); + assertEquals(1, all.get(messageId1).size()); + + // No metadata for unknown messages + db.setMessageState(txn, messageId, UNKNOWN); + db.setMessageState(txn, messageId1, UNKNOWN); + all = db.getMessageMetadata(txn, groupId, query); + assertTrue(all.isEmpty()); + + // No metadata for invalid messages + db.setMessageState(txn, messageId, INVALID); + db.setMessageState(txn, messageId1, INVALID); + all = db.getMessageMetadata(txn, groupId, query); + assertTrue(all.isEmpty()); + + // No metadata for valid messages + db.setMessageState(txn, messageId, VALID); + db.setMessageState(txn, messageId1, VALID); + all = db.getMessageMetadata(txn, groupId, query); + assertTrue(all.isEmpty()); + + // No metadata for pending messages + db.setMessageState(txn, messageId, PENDING); + db.setMessageState(txn, messageId1, PENDING); + all = db.getMessageMetadata(txn, groupId, query); + assertTrue(all.isEmpty()); + } + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testMessageDependencies() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a group and a message + db.addGroup(txn, group); + db.addMessage(txn, message, VALID, true); + + // Create more messages + MessageId mId1 = new MessageId(TestUtils.getRandomId()); + MessageId mId2 = new MessageId(TestUtils.getRandomId()); + MessageId dId1 = new MessageId(TestUtils.getRandomId()); + MessageId dId2 = new MessageId(TestUtils.getRandomId()); + Message m1 = new Message(mId1, groupId, timestamp, raw); + Message m2 = new Message(mId2, groupId, timestamp, raw); + + // Add new messages + db.addMessage(txn, m1, VALID, true); + db.addMessage(txn, m2, INVALID, true); + + // Add dependencies + db.addMessageDependency(txn, messageId, mId1); + db.addMessageDependency(txn, messageId, mId2); + db.addMessageDependency(txn, mId1, dId1); + db.addMessageDependency(txn, mId2, dId2); + + Map<MessageId, State> dependencies; + + // Retrieve dependencies for root + dependencies = db.getMessageDependencies(txn, messageId); + assertEquals(2, dependencies.size()); + assertEquals(VALID, dependencies.get(mId1)); + assertEquals(INVALID, dependencies.get(mId2)); + + // Retrieve dependencies for m1 + dependencies = db.getMessageDependencies(txn, mId1); + assertEquals(1, dependencies.size()); + assertEquals(UNKNOWN, dependencies.get(dId1)); + + // Retrieve dependencies for m2 + dependencies = db.getMessageDependencies(txn, mId2); + assertEquals(1, dependencies.size()); + assertEquals(UNKNOWN, dependencies.get(dId2)); + + // Make sure d's have no dependencies + dependencies = db.getMessageDependencies(txn, dId1); + assertTrue(dependencies.isEmpty()); + dependencies = db.getMessageDependencies(txn, dId2); + assertTrue(dependencies.isEmpty()); + + Map<MessageId, State> dependents; + + // Root message does not have dependents + dependents = db.getMessageDependents(txn, messageId); + assertTrue(dependents.isEmpty()); + + // The root message depends on both m's + dependents = db.getMessageDependents(txn, mId1); + assertEquals(1, dependents.size()); + assertEquals(VALID, dependents.get(messageId)); + dependents = db.getMessageDependents(txn, mId2); + assertEquals(1, dependents.size()); + assertEquals(VALID, dependents.get(messageId)); + + // Both m's depend on the d's + dependents = db.getMessageDependents(txn, dId1); + assertEquals(1, dependents.size()); + assertEquals(VALID, dependents.get(mId1)); + dependents = db.getMessageDependents(txn, dId2); + assertEquals(1, dependents.size()); + assertEquals(INVALID, dependents.get(mId2)); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testMessageDependenciesInSameGroup() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a group and a message + db.addGroup(txn, group); + db.addMessage(txn, message, DELIVERED, true); + + // Add a second group + GroupId groupId1 = new GroupId(TestUtils.getRandomId()); + Group group1 = new Group(groupId1, group.getClientId(), + TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH)); + db.addGroup(txn, group1); + + // Add a message to the second group + MessageId mId1 = new MessageId(TestUtils.getRandomId()); + Message m1 = new Message(mId1, groupId1, timestamp, raw); + db.addMessage(txn, m1, DELIVERED, true); + + // Create a fake dependency as well + MessageId mId2 = new MessageId(TestUtils.getRandomId()); + + // Create and add a real and proper dependency + MessageId mId3 = new MessageId(TestUtils.getRandomId()); + Message m3 = new Message(mId3, groupId, timestamp, raw); + db.addMessage(txn, m3, PENDING, true); + + // Add dependencies + db.addMessageDependency(txn, messageId, mId1); + db.addMessageDependency(txn, messageId, mId2); + db.addMessageDependency(txn, messageId, mId3); + + // Return invalid dependencies for delivered message m1 + Map<MessageId, State> dependencies; + dependencies = db.getMessageDependencies(txn, messageId); + assertEquals(INVALID, dependencies.get(mId1)); + assertEquals(UNKNOWN, dependencies.get(mId2)); + assertEquals(PENDING, dependencies.get(mId3)); + + // Return invalid dependencies for valid message m1 + db.setMessageState(txn, mId1, VALID); + dependencies = db.getMessageDependencies(txn, messageId); + assertEquals(INVALID, dependencies.get(mId1)); + assertEquals(UNKNOWN, dependencies.get(mId2)); + assertEquals(PENDING, dependencies.get(mId3)); + + // Return invalid dependencies for pending message m1 + db.setMessageState(txn, mId1, PENDING); + dependencies = db.getMessageDependencies(txn, messageId); + assertEquals(INVALID, dependencies.get(mId1)); + assertEquals(UNKNOWN, dependencies.get(mId2)); + assertEquals(PENDING, dependencies.get(mId3)); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testGetMessagesForValidationAndDelivery() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a group and a message + db.addGroup(txn, group); + db.addMessage(txn, message, VALID, true); + + // Create more messages + MessageId mId1 = new MessageId(TestUtils.getRandomId()); + MessageId mId2 = new MessageId(TestUtils.getRandomId()); + MessageId mId3 = new MessageId(TestUtils.getRandomId()); + MessageId mId4 = new MessageId(TestUtils.getRandomId()); + Message m1 = new Message(mId1, groupId, timestamp, raw); + Message m2 = new Message(mId2, groupId, timestamp, raw); + Message m3 = new Message(mId3, groupId, timestamp, raw); + Message m4 = new Message(mId4, groupId, timestamp, raw); + + // Add new messages with different states + db.addMessage(txn, m1, UNKNOWN, true); + db.addMessage(txn, m2, INVALID, true); + db.addMessage(txn, m3, PENDING, true); + db.addMessage(txn, m4, DELIVERED, true); + + Collection<MessageId> result; + + // Retrieve messages to be validated + result = db.getMessagesToValidate(txn, group.getClientId()); + assertEquals(1, result.size()); + assertTrue(result.contains(mId1)); + + // Retrieve messages to be delivered + result = db.getMessagesToDeliver(txn, group.getClientId()); + assertEquals(1, result.size()); + assertTrue(result.contains(messageId)); + + // Retrieve pending messages + result = db.getPendingMessages(txn, group.getClientId()); + assertEquals(1, result.size()); + assertTrue(result.contains(mId3)); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testGetMessageStatus() throws Exception { Database<Connection> db = open(false); @@ -1049,7 +1381,7 @@ public class H2DatabaseTest extends BriarTestCase { db.addVisibility(txn, contactId, groupId); // Add a message to the group - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); // The message should not be sent or seen @@ -1176,7 +1508,7 @@ public class H2DatabaseTest extends BriarTestCase { true)); db.addGroup(txn, group); db.addVisibility(txn, contactId, groupId); - db.addMessage(txn, message, VALID, true); + db.addMessage(txn, message, DELIVERED, true); db.addStatus(txn, contactId, messageId, false, false); // The message should be visible to the contact diff --git a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java index 1aea2e30b01fea7a8db15b210a8fb1fc5a77a345..d55b575c2d9c2d1db66aadbb6ad1326631fb2a83 100644 --- a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java +++ b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java @@ -28,6 +28,9 @@ import org.junit.Test; import java.util.Arrays; import java.util.concurrent.Executor; +import static org.briarproject.api.sync.ValidationManager.State.INVALID; +import static org.briarproject.api.sync.ValidationManager.State.VALID; + public class ValidationManagerImplTest extends BriarTestCase { private final ClientId clientId = new ClientId(TestUtils.getRandomId()); @@ -88,7 +91,7 @@ public class ValidationManagerImplTest extends BriarTestCase { oneOf(db).startTransaction(false); will(returnValue(txn2)); oneOf(db).mergeMessageMetadata(txn2, messageId, metadata); - oneOf(db).setMessageValid(txn2, message, clientId, true); + oneOf(db).setMessageState(txn2, messageId, clientId, VALID); oneOf(db).setMessageShared(txn2, message, true); // Call the hook for the first message oneOf(hook).incomingMessage(txn2, message, metadata); @@ -107,7 +110,7 @@ public class ValidationManagerImplTest extends BriarTestCase { // Store the validation result for the second message oneOf(db).startTransaction(false); will(returnValue(txn4)); - oneOf(db).setMessageValid(txn4, message1, clientId, false); + oneOf(db).setMessageState(txn4, messageId1, clientId, INVALID); oneOf(db).endTransaction(txn4); }}); @@ -161,7 +164,7 @@ public class ValidationManagerImplTest extends BriarTestCase { // Store the validation result for the second message oneOf(db).startTransaction(false); will(returnValue(txn3)); - oneOf(db).setMessageValid(txn3, message1, clientId, false); + oneOf(db).setMessageState(txn3, messageId1, clientId, INVALID); oneOf(db).endTransaction(txn3); }}); @@ -218,7 +221,7 @@ public class ValidationManagerImplTest extends BriarTestCase { // Store the validation result for the second message oneOf(db).startTransaction(false); will(returnValue(txn3)); - oneOf(db).setMessageValid(txn3, message1, clientId, false); + oneOf(db).setMessageState(txn3, messageId1, clientId, INVALID); oneOf(db).endTransaction(txn3); }}); @@ -256,7 +259,7 @@ public class ValidationManagerImplTest extends BriarTestCase { oneOf(db).startTransaction(false); will(returnValue(txn1)); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); - oneOf(db).setMessageValid(txn1, message, clientId, true); + oneOf(db).setMessageState(txn1, messageId, clientId, VALID); oneOf(db).setMessageShared(txn1, message, true); // Call the hook oneOf(hook).incomingMessage(txn1, message, metadata);