Refactor ValidationManager and fix some bugs. #619

This commit is contained in:
akwizgran
2016-09-08 14:57:41 +01:00
parent fd4275733f
commit 8a3e5bfb50
34 changed files with 978 additions and 922 deletions

View File

@@ -58,7 +58,6 @@ import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.briarproject.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -536,14 +535,14 @@ public class BlogManagerTest {
} }
private class Listener implements EventListener { private class Listener implements EventListener {
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
if (!event.isLocal()) { if (!event.isLocal()) {
if (event.getState() == DELIVERED) { if (event.getState() == DELIVERED) {
deliveryWaiter.resume(); deliveryWaiter.resume();
} else if (event.getState() == VALID || } else if (event.getState() == INVALID ||
event.getState() == INVALID ||
event.getState() == PENDING) { event.getState() == PENDING) {
validationWaiter.resume(); validationWaiter.resume();
} }

View File

@@ -25,7 +25,6 @@ import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.SyncSession; import org.briarproject.api.sync.SyncSession;
import org.briarproject.api.sync.SyncSessionFactory; import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.sync.ValidationManager.State;
@@ -424,7 +423,7 @@ public class BlogSharingIntegrationTest extends BriarTestCase {
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived); assertTrue(listener0.responseReceived);
// blog was added successfully and is shard both ways // blog was added successfully and is shared both ways
assertEquals(3, blogManager1.getBlogs().size()); assertEquals(3, blogManager1.getBlogs().size());
Collection<Contact> sharedWith = Collection<Contact> sharedWith =
blogSharingManager0.getSharedWith(blog2.getId()); blogSharingManager0.getSharedWith(blog2.getId());
@@ -514,20 +513,13 @@ public class BlogSharingIntegrationTest extends BriarTestCase {
volatile boolean requestReceived = false; volatile boolean requestReceived = false;
volatile boolean responseReceived = false; volatile boolean responseReceived = false;
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState(); State s = event.getState();
ClientId c = event.getClientId(); if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
if ((s == DELIVERED || s == INVALID) && LOG.info("TEST: Sharer received message");
c.equals(blogSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Sharer received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(blogManager0.getClientId())) {
LOG.info("TEST: Sharer received blog post");
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof BlogInvitationResponseReceivedEvent) { } else if (e instanceof BlogInvitationResponseReceivedEvent) {
@@ -572,20 +564,13 @@ public class BlogSharingIntegrationTest extends BriarTestCase {
this(accept, true); this(accept, true);
} }
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState(); State s = event.getState();
ClientId c = event.getClientId(); if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
if ((s == DELIVERED || s == INVALID) && LOG.info("TEST: Invitee received message");
c.equals(blogSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Invitee received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(blogManager0.getClientId())) {
LOG.info("TEST: Invitee received blog post");
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof BlogInvitationReceivedEvent) { } else if (e instanceof BlogInvitationReceivedEvent) {

View File

@@ -57,7 +57,6 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGT
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.briarproject.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ForumManagerTest { public class ForumManagerTest {
@@ -298,7 +297,7 @@ public class ForumManagerTest {
assertEquals(1, forumManager0.getPostHeaders(g).size()); assertEquals(1, forumManager0.getPostHeaders(g).size());
assertEquals(0, forumManager1.getPostHeaders(g).size()); assertEquals(0, forumManager1.getPostHeaders(g).size());
// send posts to 1 // send the child post to 1
sync0To1(); sync0To1();
validationWaiter.await(TIMEOUT, 1); validationWaiter.await(TIMEOUT, 1);
assertEquals(1, forumManager0.getPostHeaders(g).size()); assertEquals(1, forumManager0.getPostHeaders(g).size());
@@ -327,14 +326,14 @@ public class ForumManagerTest {
} }
private class Listener implements EventListener { private class Listener implements EventListener {
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
if (!event.isLocal()) { if (!event.isLocal()) {
if (event.getState() == DELIVERED) { if (event.getState() == DELIVERED) {
deliveryWaiter.resume(); deliveryWaiter.resume();
} else if (event.getState() == VALID || } else if (event.getState() == INVALID ||
event.getState() == INVALID ||
event.getState() == PENDING) { event.getState() == PENDING) {
validationWaiter.resume(); validationWaiter.resume();
} }

View File

@@ -38,7 +38,6 @@ import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sharing.InvitationItem; import org.briarproject.api.sharing.InvitationItem;
import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.InvitationMessage;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.SyncSession; import org.briarproject.api.sync.SyncSession;
import org.briarproject.api.sync.SyncSessionFactory; import org.briarproject.api.sync.SyncSessionFactory;
@@ -928,20 +927,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
volatile boolean requestReceived = false; volatile boolean requestReceived = false;
volatile boolean responseReceived = false; volatile boolean responseReceived = false;
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState(); State s = event.getState();
ClientId c = event.getClientId(); if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
if ((s == DELIVERED || s == INVALID) && LOG.info("TEST: Sharer received message");
c.equals(forumSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Sharer received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(forumManager0.getClientId())) {
LOG.info("TEST: Sharer received forum post");
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof ForumInvitationResponseReceivedEvent) { } else if (e instanceof ForumInvitationResponseReceivedEvent) {
@@ -986,20 +978,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
this(accept, true); this(accept, true);
} }
@Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState(); State s = event.getState();
ClientId c = event.getClientId(); if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
if ((s == DELIVERED || s == INVALID) && LOG.info("TEST: Invitee received message");
c.equals(forumSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Invitee received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume();
} else if (s == DELIVERED && !event.isLocal() &&
c.equals(forumManager0.getClientId())) {
LOG.info("TEST: Invitee received forum post");
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof ForumInvitationReceivedEvent) { } else if (e instanceof ForumInvitationReceivedEvent) {

View File

@@ -36,7 +36,6 @@ import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
@@ -1107,13 +1106,9 @@ public class IntroductionIntegrationTest extends BriarTestCase {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
State s = event.getState(); State s = event.getState();
ClientId c = event.getClientId(); if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
if ((s == DELIVERED || s == INVALID) &&
c.equals(introductionManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Introducee" + introducee + LOG.info("TEST: Introducee" + introducee +
" received message in group " + " received message");
event.getMessage().getGroupId().hashCode());
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof IntroductionRequestReceivedEvent) { } else if (e instanceof IntroductionRequestReceivedEvent) {
@@ -1174,11 +1169,8 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof MessageStateChangedEvent) { if (e instanceof MessageStateChangedEvent) {
MessageStateChangedEvent event = (MessageStateChangedEvent) e; MessageStateChangedEvent event = (MessageStateChangedEvent) e;
if (event.getState() == DELIVERED && event.getClientId() if (event.getState() == DELIVERED && !event.isLocal()) {
.equals(introductionManager0.getClientId()) && LOG.info("TEST: Introducer received message");
!event.isLocal()) {
LOG.info("TEST: Introducer received message in group " +
event.getMessage().getGroupId().hashCode());
msgWaiter.resume(); msgWaiter.resume();
} }
} else if (e instanceof IntroductionResponseReceivedEvent) { } else if (e instanceof IntroductionResponseReceivedEvent) {

View File

@@ -3,22 +3,24 @@ package org.briarproject.api.clients;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.BaseMessageContext; import org.briarproject.api.sync.BaseMessageContext;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
public class BdfMessageContext extends BaseMessageContext { public class BdfMessageContext extends BaseMessageContext {
private final BdfDictionary dictionary; private final BdfDictionary dictionary;
public BdfMessageContext(BdfDictionary dictionary, public BdfMessageContext(@NotNull BdfDictionary dictionary,
Collection<MessageId> dependencies) { @NotNull Collection<MessageId> dependencies) {
super(dependencies); super(dependencies);
this.dictionary = dictionary; this.dictionary = dictionary;
} }
public BdfMessageContext(BdfDictionary dictionary) { public BdfMessageContext(@NotNull BdfDictionary dictionary) {
this(dictionary, null); this(dictionary, Collections.<MessageId>emptyList());
} }
public BdfDictionary getDictionary() { public BdfDictionary getDictionary() {

View File

@@ -5,7 +5,6 @@ import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
@@ -15,12 +14,11 @@ import java.util.Map;
public interface ClientHelper { public interface ClientHelper {
void addLocalMessage(Message m, ClientId c, BdfDictionary metadata, void addLocalMessage(Message m, BdfDictionary metadata, boolean shared)
boolean shared) throws DbException, FormatException; throws DbException, FormatException;
void addLocalMessage(Transaction txn, Message m, ClientId c, void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
BdfDictionary metadata, boolean shared) throws DbException, boolean shared) throws DbException, FormatException;
FormatException;
Message createMessage(GroupId g, long timestamp, BdfDictionary body) Message createMessage(GroupId g, long timestamp, BdfDictionary body)
throws FormatException; throws FormatException;

View File

@@ -18,6 +18,7 @@ import org.briarproject.api.sync.MessageStatus;
import org.briarproject.api.sync.Offer; import org.briarproject.api.sync.Offer;
import org.briarproject.api.sync.Request; import org.briarproject.api.sync.Request;
import org.briarproject.api.transport.TransportKeys; import org.briarproject.api.transport.TransportKeys;
import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -77,7 +78,7 @@ public interface DatabaseComponent {
/** /**
* Stores a local message. * Stores a local message.
*/ */
void addLocalMessage(Transaction txn, Message m, ClientId c, Metadata meta, void addLocalMessage(Transaction txn, Message m, Metadata meta,
boolean shared) throws DbException; boolean shared) throws DbException;
/** /**
@@ -125,6 +126,7 @@ public interface DatabaseComponent {
* Returns an acknowledgement for the given contact, or null if there are * Returns an acknowledgement for the given contact, or null if there are
* no messages to acknowledge. * no messages to acknowledge.
*/ */
@Nullable
Ack generateAck(Transaction txn, ContactId c, int maxMessages) Ack generateAck(Transaction txn, ContactId c, int maxMessages)
throws DbException; throws DbException;
@@ -134,6 +136,7 @@ public interface DatabaseComponent {
* transport with the given maximum latency. Returns null if there are no * transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
@Nullable
Collection<byte[]> generateBatch(Transaction txn, ContactId c, Collection<byte[]> generateBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
@@ -142,6 +145,7 @@ public interface DatabaseComponent {
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
* messages to offer. * messages to offer.
*/ */
@Nullable
Offer generateOffer(Transaction txn, ContactId c, int maxMessages, Offer generateOffer(Transaction txn, ContactId c, int maxMessages,
int maxLatency) throws DbException; int maxLatency) throws DbException;
@@ -149,6 +153,7 @@ public interface DatabaseComponent {
* Returns a request for the given contact, or null if there are no * Returns a request for the given contact, or null if there are no
* messages to request. * messages to request.
*/ */
@Nullable
Request generateRequest(Transaction txn, ContactId c, int maxMessages) Request generateRequest(Transaction txn, ContactId c, int maxMessages)
throws DbException; throws DbException;
@@ -159,6 +164,7 @@ public interface DatabaseComponent {
* requested by the contact are returned. Returns null if there are no * requested by the contact are returned. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
@Nullable
Collection<byte[]> generateRequestedBatch(Transaction txn, ContactId c, Collection<byte[]> generateRequestedBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
@@ -244,17 +250,8 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that need to be delivered to the given * Returns the IDs of any messages that are valid but pending delivery due
* client. * to dependencies on other messages for 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/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -267,6 +264,7 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@Nullable
byte[] getRawMessage(Transaction txn, MessageId m) throws DbException; byte[] getRawMessage(Transaction txn, MessageId m) throws DbException;
/** /**
@@ -314,7 +312,13 @@ public interface DatabaseComponent {
GroupId g) throws DbException; GroupId g) throws DbException;
/** /**
* Returns the dependencies of the given message. * Returns the IDs and states of all dependencies of the given message.
* Missing dependencies have the state {@link
* org.briarproject.api.sync.ValidationManager.State UNKNOWN}.
* Dependencies in other groups have the state {@link
* org.briarproject.api.sync.ValidationManager.State INVALID}.
* Note that these states are not set on the dependencies themselves; the
* returned states should only be taken in the context of the given message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -323,12 +327,21 @@ public interface DatabaseComponent {
/** /**
* Returns all IDs of messages that depend on the given message. * Returns all IDs of messages that depend on the given message.
* Messages in other groups that declare a dependency on the given message
* will be returned even though such dependencies are invalid.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<MessageId, State> getMessageDependents(Transaction txn, MessageId m) Map<MessageId, State> getMessageDependents(Transaction txn, MessageId m)
throws DbException; throws DbException;
/**
* Gets the validation and delivery state of the given message.
* <p/>
* Read-only.
*/
State getMessageState(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given message with respect to the given
* contact. * contact.
@@ -449,9 +462,9 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Sets the state of the message with respect to validation and delivery. * Sets the validation and delivery state of the given message.
*/ */
void setMessageState(Transaction txn, Message m, ClientId c, State valid) void setMessageState(Transaction txn, MessageId m, State state)
throws DbException; throws DbException;
/** /**

View File

@@ -1,8 +1,7 @@
package org.briarproject.api.event; package org.briarproject.api.event;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.ValidationManager;
import static org.briarproject.api.sync.ValidationManager.State; import static org.briarproject.api.sync.ValidationManager.State;
/** /**
@@ -10,25 +9,19 @@ import static org.briarproject.api.sync.ValidationManager.State;
*/ */
public class MessageStateChangedEvent extends Event { public class MessageStateChangedEvent extends Event {
private final Message message; private final MessageId messageId;
private final ClientId clientId;
private final boolean local; private final boolean local;
private final State state; private final State state;
public MessageStateChangedEvent(Message message, ClientId clientId, public MessageStateChangedEvent(MessageId messageId, boolean local,
boolean local, State state) { State state) {
this.message = message; this.messageId = messageId;
this.clientId = clientId;
this.local = local; this.local = local;
this.state = state; this.state = state;
} }
public Message getMessage() { public MessageId getMessageId() {
return message; return messageId;
}
public ClientId getClientId() {
return clientId;
} }
public boolean isLocal() { public boolean isLocal() {

View File

@@ -1,12 +1,14 @@
package org.briarproject.api.sync; package org.briarproject.api.sync;
import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
public abstract class BaseMessageContext { public abstract class BaseMessageContext {
private final Collection<MessageId> dependencies; private final Collection<MessageId> dependencies;
public BaseMessageContext(Collection<MessageId> dependencies) { public BaseMessageContext(@NotNull Collection<MessageId> dependencies) {
this.dependencies = dependencies; this.dependencies = dependencies;
} }

View File

@@ -1,22 +1,24 @@
package org.briarproject.api.sync; package org.briarproject.api.sync;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
public class MessageContext extends BaseMessageContext { public class MessageContext extends BaseMessageContext {
private final Metadata metadata; private final Metadata metadata;
public MessageContext(Metadata metadata, public MessageContext(@NotNull Metadata metadata,
Collection<MessageId> dependencies) { @NotNull Collection<MessageId> dependencies) {
super(dependencies); super(dependencies);
this.metadata = metadata; this.metadata = metadata;
} }
public MessageContext(Metadata metadata) { public MessageContext(@NotNull Metadata metadata) {
this(metadata, null); this(metadata, Collections.<MessageId>emptyList());
} }
public Metadata getMetadata() { public Metadata getMetadata() {

View File

@@ -12,7 +12,7 @@ public interface ValidationManager {
enum State { enum State {
UNKNOWN(0), INVALID(1), PENDING(2), VALID(3), DELIVERED(4); UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
private final int value; private final int value;

View File

@@ -59,12 +59,12 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
@@ -298,8 +298,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor())); meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
meta.put(KEY_READ, true); meta.put(KEY_READ, true);
clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
true);
// broadcast event about new post // broadcast event about new post
GroupId groupId = p.getMessage().getGroupId(); GroupId groupId = p.getMessage().getGroupId();
@@ -346,7 +345,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
meta.put(KEY_AUTHOR, authorToBdfDictionary(author)); meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
// Send comment // Send comment
clientHelper.addLocalMessage(txn, message, CLIENT_ID, meta, true); clientHelper.addLocalMessage(txn, message, meta, true);
// broadcast event // broadcast event
BlogPostHeader h = BlogPostHeader h =
@@ -429,7 +428,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived()); meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
// Send wrapped message and store metadata // Send wrapped message and store metadata
clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true); clientHelper.addLocalMessage(txn, wMessage, meta, true);
return wMessage.getId(); return wMessage.getId();
} }

View File

@@ -32,9 +32,9 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT; import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
@@ -114,7 +114,7 @@ class BlogPostValidator extends BdfMessageValidator {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_ORIGINAL_MSG_ID, m.getId()); meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
return new BdfMessageContext(meta, null); return new BdfMessageContext(meta);
} }
private BdfMessageContext validateComment(Message m, Group g, BdfList body) private BdfMessageContext validateComment(Message m, Group g, BdfList body)
@@ -197,7 +197,7 @@ class BlogPostValidator extends BdfMessageValidator {
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId()); meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
meta.put(KEY_TIMESTAMP, wTimestamp); meta.put(KEY_TIMESTAMP, wTimestamp);
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
return new BdfMessageContext(meta, null); return new BdfMessageContext(meta);
} }
private BdfMessageContext validateWrappedComment(Message m, Group g, private BdfMessageContext validateWrappedComment(Message m, Group g,

View File

@@ -18,7 +18,6 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory; import org.briarproject.api.sync.MessageFactory;
@@ -62,11 +61,11 @@ class ClientHelperImpl implements ClientHelper {
} }
@Override @Override
public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata, public void addLocalMessage(Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException { boolean shared) throws DbException, FormatException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
addLocalMessage(txn, m, c, metadata, shared); addLocalMessage(txn, m, metadata, shared);
txn.setComplete(); txn.setComplete();
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -74,10 +73,10 @@ class ClientHelperImpl implements ClientHelper {
} }
@Override @Override
public void addLocalMessage(Transaction txn, Message m, ClientId c, public void addLocalMessage(Transaction txn, Message m,
BdfDictionary metadata, boolean shared) BdfDictionary metadata, boolean shared)
throws DbException, FormatException { throws DbException, FormatException {
db.addLocalMessage(txn, m, c, metadataEncoder.encode(metadata), shared); db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared);
} }
@Override @Override

View File

@@ -16,10 +16,10 @@ import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import java.util.ArrayList; import java.util.ArrayList;
@@ -70,7 +70,7 @@ class MessageQueueManagerImpl implements MessageQueueManager {
saveQueueState(txn, queue.getId(), queueState); saveQueueState(txn, queue.getId(), queueState);
QueueMessage q = queueMessageFactory.createMessage(queue.getId(), QueueMessage q = queueMessageFactory.createMessage(queue.getId(),
timestamp, queuePosition, body); timestamp, queuePosition, body);
db.addLocalMessage(txn, q, queue.getClientId(), meta, true); db.addLocalMessage(txn, q, meta, true);
return q; return q;
} }

View File

@@ -18,6 +18,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.MessageStatus; import org.briarproject.api.sync.MessageStatus;
import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.transport.TransportKeys; import org.briarproject.api.transport.TransportKeys;
import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -83,10 +84,10 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Adds a dependency between two MessageIds * Adds a dependency between two messages in the given group.
*/ */
void addMessageDependency(T txn, MessageId dependentId, void addMessageDependency(T txn, GroupId g, MessageId dependent,
MessageId dependencyId) throws DbException; MessageId dependency) throws DbException;
/** /**
* Records that a message has been offered by the given contact. * Records that a message has been offered by the given contact.
@@ -281,11 +282,13 @@ interface Database<T> {
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
/** /**
* Returns the dependencies of the given message. * Returns the IDs and states of all dependencies of the given message.
* This method makes sure that dependencies in different groups * Missing dependencies have the state {@link
* are returned as {@link ValidationManager.State.INVALID}. Note that this * org.briarproject.api.sync.ValidationManager.State UNKNOWN}.
* is not set on the dependencies themselves; the returned states should * Dependencies in other groups have the state {@link
* only be taken in the context of the given message. * org.briarproject.api.sync.ValidationManager.State INVALID}.
* Note that these states are not set on the dependencies themselves; the
* returned states should only be taken in the context of the given message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -293,7 +296,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns all IDs of messages that depend on the given message. * Returns all IDs and states of all dependents of the given message.
* Messages in other groups that declare a dependency on the given message
* will be returned even though such dependencies are invalid.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -350,6 +355,13 @@ interface Database<T> {
*/ */
Metadata getMessageMetadata(T txn, MessageId m) throws DbException; Metadata getMessageMetadata(T txn, MessageId m) throws DbException;
/**
* Returns the validation and delivery state of the given message.
* <p/>
* Read-only.
*/
State getMessageState(T txn, MessageId m) throws DbException;
/** /**
* Returns the status of all messages in the given group with respect * Returns the status of all messages in the given group with respect
* to the given contact. * to the given contact.
@@ -377,15 +389,6 @@ interface Database<T> {
Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages) Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
throws DbException; 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 some messages that are eligible to be offered to the * Returns the IDs of some messages that are eligible to be offered to the
* given contact, up to the given number of messages. * given contact, up to the given number of messages.
@@ -437,6 +440,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@Nullable
byte[] getRawMessage(T txn, MessageId m) throws DbException; byte[] getRawMessage(T txn, MessageId m) throws DbException;
/** /**
@@ -592,7 +596,7 @@ interface Database<T> {
* Marks the given contact as active or inactive. * Marks the given contact as active or inactive.
*/ */
void setContactActive(T txn, ContactId c, boolean active) void setContactActive(T txn, ContactId c, boolean active)
throws DbException; throws DbException;
/** /**
* Marks the given message as shared or unshared. * Marks the given message as shared or unshared.
@@ -601,10 +605,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Marks the given message as valid or invalid. * Sets the validation and delivery state of the given message.
*/ */
void setMessageState(T txn, MessageId m, State state) void setMessageState(T txn, MessageId m, State state) throws DbException;
throws DbException;
/** /**
* Sets the reordering window for the given contact and transport in the * Sets the reordering window for the given contact and transport in the

View File

@@ -28,10 +28,10 @@ import org.briarproject.api.event.LocalAuthorRemovedEvent;
import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageRequestedEvent;
import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageSharedEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToAckEvent;
import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.MessageToRequestEvent;
import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
@@ -50,6 +50,7 @@ import org.briarproject.api.sync.Offer;
import org.briarproject.api.sync.Request; import org.briarproject.api.sync.Request;
import org.briarproject.api.sync.ValidationManager.State; import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.transport.TransportKeys; import org.briarproject.api.transport.TransportKeys;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -188,7 +189,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
public void addLocalMessage(Transaction transaction, Message m, ClientId c, public void addLocalMessage(Transaction transaction, Message m,
Metadata meta, boolean shared) throws DbException { Metadata meta, boolean shared) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
@@ -197,7 +198,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsMessage(txn, m.getId())) { if (!db.containsMessage(txn, m.getId())) {
addMessage(txn, m, DELIVERED, shared); addMessage(txn, m, DELIVERED, shared);
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m, c, true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
if (shared) transaction.attach(new MessageSharedEvent(m.getId())); if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
} }
@@ -271,6 +272,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.deleteMessageMetadata(txn, m); db.deleteMessageMetadata(txn, m);
} }
@Nullable
public Ack generateAck(Transaction transaction, ContactId c, public Ack generateAck(Transaction transaction, ContactId c,
int maxMessages) throws DbException { int maxMessages) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -283,6 +285,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Ack(ids); return new Ack(ids);
} }
@Nullable
public Collection<byte[]> generateBatch(Transaction transaction, public Collection<byte[]> generateBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException { ContactId c, int maxLength, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -301,6 +304,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return Collections.unmodifiableList(messages); return Collections.unmodifiableList(messages);
} }
@Nullable
public Offer generateOffer(Transaction transaction, ContactId c, public Offer generateOffer(Transaction transaction, ContactId c,
int maxMessages, int maxLatency) throws DbException { int maxMessages, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -313,6 +317,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Offer(ids); return new Offer(ids);
} }
@Nullable
public Request generateRequest(Transaction transaction, ContactId c, public Request generateRequest(Transaction transaction, ContactId c,
int maxMessages) throws DbException { int maxMessages) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -326,6 +331,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return new Request(ids); return new Request(ids);
} }
@Nullable
public Collection<byte[]> generateRequestedBatch(Transaction transaction, public Collection<byte[]> generateRequestedBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException { ContactId c, int maxLength, int maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -420,18 +426,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessagesToValidate(txn, c); 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, public Collection<MessageId> getPendingMessages(Transaction transaction,
ClientId c) throws DbException { ClientId c) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getPendingMessages(txn, c); return db.getPendingMessages(txn, c);
} }
@Nullable
public byte[] getRawMessage(Transaction transaction, MessageId m) public byte[] getRawMessage(Transaction transaction, MessageId m)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
@@ -473,6 +474,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageMetadataForValidator(txn, m); return db.getMessageMetadataForValidator(txn, m);
} }
public State getMessageState(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getMessageState(txn, m);
}
public Collection<MessageStatus> getMessageStatus(Transaction transaction, public Collection<MessageStatus> getMessageStatus(Transaction transaction,
ContactId c, GroupId g) throws DbException { ContactId c, GroupId g) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
@@ -720,14 +729,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (shared) transaction.attach(new MessageSharedEvent(m)); if (shared) transaction.attach(new MessageSharedEvent(m));
} }
public void setMessageState(Transaction transaction, Message m, ClientId c, public void setMessageState(Transaction transaction, MessageId m,
State state) throws DbException { State state) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m.getId())) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
db.setMessageState(txn, m.getId(), state); db.setMessageState(txn, m, state);
transaction.attach(new MessageStateChangedEvent(m, c, false, state)); transaction.attach(new MessageStateChangedEvent(m, false, state));
} }
public void addMessageDependencies(Transaction transaction, public void addMessageDependencies(Transaction transaction,
@@ -737,8 +746,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, dependent.getId())) if (!db.containsMessage(txn, dependent.getId()))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
for (MessageId dependencyId : dependencies) { for (MessageId dependency : dependencies) {
db.addMessageDependency(txn, dependent.getId(), dependencyId); db.addMessageDependency(txn, dependent.getGroupId(),
dependent.getId(), dependency);
} }
} }

View File

@@ -25,6 +25,7 @@ import org.briarproject.api.transport.IncomingKeys;
import org.briarproject.api.transport.OutgoingKeys; import org.briarproject.api.transport.OutgoingKeys;
import org.briarproject.api.transport.TransportKeys; import org.briarproject.api.transport.TransportKeys;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.sql.Connection; import java.sql.Connection;
@@ -53,7 +54,6 @@ 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; 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.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.DB_SETTINGS_NAMESPACE;
import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY; import static org.briarproject.db.DatabaseConstants.DEVICE_ID_KEY;
import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE; import static org.briarproject.db.DatabaseConstants.DEVICE_SETTINGS_NAMESPACE;
@@ -67,8 +67,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
*/ */
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 26; private static final int SCHEMA_VERSION = 27;
private static final int MIN_SCHEMA_VERSION = 26; private static final int MIN_SCHEMA_VERSION = 27;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -155,8 +155,12 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_MESSAGE_DEPENDENCIES = private static final String CREATE_MESSAGE_DEPENDENCIES =
"CREATE TABLE messageDependencies" "CREATE TABLE messageDependencies"
+ " (messageId HASH NOT NULL," + " (groupId HASH NOT NULL,"
+ " messageId HASH NOT NULL,"
+ " dependencyId HASH NOT NULL," // Not a foreign key + " dependencyId HASH NOT NULL," // Not a foreign key
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (messageId)" + " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)" + " REFERENCES messages (messageId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
@@ -609,16 +613,17 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public void addMessageDependency(Connection txn, MessageId dependentId, public void addMessageDependency(Connection txn, GroupId g,
MessageId dependencyId) throws DbException { MessageId dependent, MessageId dependency) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = String sql = "INSERT INTO messageDependencies"
"INSERT INTO messageDependencies (messageId, dependencyId)" + " (groupId, messageId, dependencyId)"
+ " VALUES (?, ?)"; + " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, dependentId.getBytes()); ps.setBytes(1, g.getBytes());
ps.setBytes(2, dependencyId.getBytes()); ps.setBytes(2, dependent.getBytes());
ps.setBytes(3, dependency.getBytes());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -1453,7 +1458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT d.dependencyId, m.state, m.groupId" String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
+ " FROM messageDependencies AS d" + " FROM messageDependencies AS d"
+ " LEFT OUTER JOIN messages AS m" + " LEFT OUTER JOIN messages AS m"
+ " ON d.dependencyId = m.messageId" + " ON d.dependencyId = m.messageId"
@@ -1463,13 +1468,17 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependencies = new HashMap<MessageId, State>(); Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
while (rs.next()) { while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1)); MessageId dependency = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); State state = State.fromValue(rs.getInt(2));
if (state != UNKNOWN) { if (rs.wasNull()) {
// set dependency invalid if it is in a different group state = UNKNOWN; // Missing dependency
if (!hasGroupId(txn, m, rs.getBytes(3))) state = INVALID; } else {
GroupId dependentGroupId = new GroupId(rs.getBytes(3));
GroupId dependencyGroupId = new GroupId(rs.getBytes(4));
if (!dependentGroupId.equals(dependencyGroupId))
state = INVALID; // Dependency in another group
} }
dependencies.put(messageId, state); dependencies.put(dependency, state);
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1481,29 +1490,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
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, public Map<MessageId, State> getMessageDependents(Connection txn,
MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -1511,7 +1497,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
String sql = "SELECT d.messageId, m.state" String sql = "SELECT d.messageId, m.state"
+ " FROM messageDependencies AS d" + " FROM messageDependencies AS d"
+ " LEFT OUTER JOIN messages AS m" + " JOIN messages AS m"
+ " ON d.messageId = m.messageId" + " ON d.messageId = m.messageId"
+ " WHERE dependencyId = ?"; + " WHERE dependencyId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
@@ -1519,9 +1505,9 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependents = new HashMap<MessageId, State>(); Map<MessageId, State> dependents = new HashMap<MessageId, State>();
while (rs.next()) { while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1)); MessageId dependent = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); State state = State.fromValue(rs.getInt(2));
dependents.put(messageId, state); dependents.put(dependent, state);
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1533,6 +1519,28 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public State getMessageState(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT state FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
State state = State.fromValue(rs.getInt(1));
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return state;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c, public Collection<MessageId> getMessagesToAck(Connection txn, ContactId c,
int maxMessages) throws DbException { int maxMessages) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -1655,11 +1663,6 @@ abstract class JdbcDatabase implements Database<Connection> {
return getMessagesInState(txn, c, UNKNOWN); 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, public Collection<MessageId> getPendingMessages(Connection txn,
ClientId c) throws DbException { ClientId c) throws DbException {
return getMessagesInState(txn, c, PENDING); return getMessagesInState(txn, c, PENDING);
@@ -1689,6 +1692,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Nullable
public byte[] getRawMessage(Connection txn, MessageId m) public byte[] getRawMessage(Connection txn, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;

View File

@@ -33,7 +33,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -49,9 +48,6 @@ import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager { class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
private static final Logger LOG =
Logger.getLogger(ForumManagerImpl.class.getName());
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString( static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"859a7be50dca035b64bd6902fb797097" "859a7be50dca035b64bd6902fb797097"
+ "795af837abbf8c16d750b3c2ccc186ea")); + "795af837abbf8c16d750b3c2ccc186ea"));
@@ -133,7 +129,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
} }
meta.put(KEY_LOCAL, true); meta.put(KEY_LOCAL, true);
meta.put(KEY_READ, true); meta.put(KEY_READ, true);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); clientHelper.addLocalMessage(p.getMessage(), meta, true);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -101,7 +101,7 @@ class ForumPostValidator extends BdfMessageValidator {
} }
// Return the metadata and dependencies // Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
Collection<MessageId> dependencies = null; Collection<MessageId> dependencies = Collections.emptyList();
meta.put("timestamp", m.getTimestamp()); meta.put("timestamp", m.getTimestamp());
if (parent != null) { if (parent != null) {
meta.put("parent", parent); meta.put("parent", parent);

View File

@@ -177,8 +177,7 @@ class IntroduceeManager {
d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity); d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity);
// save local state to database // save local state to database
clientHelper.addLocalMessage(txn, localMsg, clientHelper.addLocalMessage(txn, localMsg, d, false);
IntroductionManagerImpl.CLIENT_ID, d, false);
return d; return d;
} }

View File

@@ -98,9 +98,7 @@ class IntroducerManager {
d.put(AUTHOR_ID_2, c2.getAuthor().getId()); d.put(AUTHOR_ID_2, c2.getAuthor().getId());
// save local state to database // save local state to database
clientHelper clientHelper.addLocalMessage(txn, m, d, false);
.addLocalMessage(txn, m, IntroductionManagerImpl.CLIENT_ID, d,
false);
return d; return d;
} }

View File

@@ -117,7 +117,7 @@ class MessagingManagerImpl extends BdfIncomingMessageHook
meta.put("contentType", m.getContentType()); meta.put("contentType", m.getContentType());
meta.put("local", true); meta.put("local", true);
meta.put("read", true); meta.put("read", true);
clientHelper.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true); clientHelper.addLocalMessage(m.getMessage(), meta, true);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -246,7 +246,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
meta.put("transportId", t.getString()); meta.put("transportId", t.getString());
meta.put("version", version); meta.put("version", version);
meta.put("local", local); meta.put("local", local);
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, shared); clientHelper.addLocalMessage(txn, m, meta, shared);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -626,7 +626,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
// save local state to database // save local state to database
BdfDictionary d = s.toBdfDictionary(); BdfDictionary d = s.toBdfDictionary();
clientHelper.addLocalMessage(txn, m, getClientId(), d, false); clientHelper.addLocalMessage(txn, m, d, false);
return s; return s;
} }
@@ -652,7 +652,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS
// save local state to database // save local state to database
BdfDictionary d = s.toBdfDictionary(); BdfDictionary d = s.toBdfDictionary();
clientHelper.addLocalMessage(txn, m, getClientId(), d, false); clientHelper.addLocalMessage(txn, m, d, false);
return s; return s;
} }

View File

@@ -41,7 +41,6 @@ import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.briarproject.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
class ValidationManagerImpl implements ValidationManager, Service, class ValidationManagerImpl implements ValidationManager, Service,
EventListener { EventListener {
@@ -71,8 +70,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
public void startService() { public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
for (ClientId c : validators.keySet()) { for (ClientId c : validators.keySet()) {
validateOutstandingMessages(c); validateOutstandingMessagesAsync(c);
deliverOutstandingMessages(c); deliverOutstandingMessagesAsync(c);
} }
} }
@@ -91,168 +90,156 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook); hooks.put(c, hook);
} }
private void validateOutstandingMessages(final ClientId c) { private void validateOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { validateOutstandingMessages(c);
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true);
try {
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) { private void validateOutstandingMessages(ClientId c) {
try {
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true);
try {
unvalidated.addAll(db.getMessagesToValidate(txn, c));
txn.setComplete();
} finally {
db.endTransaction(txn);
}
validateNextMessageAsync(unvalidated);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void validateNextMessageAsync(final Queue<MessageId> unvalidated) {
if (unvalidated.isEmpty()) return; if (unvalidated.isEmpty()) return;
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { validateNextMessage(unvalidated);
Message m = null;
Group g = null;
Transaction txn = db.startTransaction(true);
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 {
if (!txn.isComplete()) txn.setComplete();
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);
}
} }
}); });
} }
private void deliverOutstandingMessages(final ClientId c) { private void validateNextMessage(Queue<MessageId> unvalidated) {
try {
Message m;
Group g;
Transaction txn = db.startTransaction(true);
try {
MessageId id = unvalidated.poll();
byte[] raw = db.getRawMessage(txn, id);
m = parseMessage(id, raw);
g = db.getGroup(txn, m.getGroupId());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
validateMessageAsync(m, g);
validateNextMessageAsync(unvalidated);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before validation");
validateNextMessageAsync(unvalidated);
} catch (NoSuchGroupException e) {
LOG.info("Group removed before validation");
validateNextMessageAsync(unvalidated);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void deliverOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { deliverOutstandingMessages(c);
Queue<MessageId> validated = new LinkedList<MessageId>();
Queue<MessageId> pending = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true);
try {
validated.addAll(db.getMessagesToDeliver(txn, c));
pending.addAll(db.getPendingMessages(txn, c));
txn.setComplete();
} finally {
db.endTransaction(txn);
}
deliverNextMessage(validated);
deliverNextPendingMessage(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
private void deliverNextMessage(final Queue<MessageId> validated) { private void deliverOutstandingMessages(ClientId c) {
if (validated.isEmpty()) return; try {
dbExecutor.execute(new Runnable() { Queue<MessageId> pending = new LinkedList<MessageId>();
@Override Transaction txn = db.startTransaction(true);
public void run() { try {
try { pending.addAll(db.getPendingMessages(txn, c));
Message m = null; txn.setComplete();
Group g = null; } finally {
Metadata meta = null; db.endTransaction(txn);
Transaction txn = db.startTransaction(true);
try {
MessageId id = validated.poll();
byte[] raw = db.getRawMessage(txn, id);
m = parseMessage(id, raw);
g = db.getGroup(txn, m.getGroupId());
meta = db.getMessageMetadataForValidator(txn, id);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (g != null) deliverMessage(m, g.getClientId(), meta);
deliverNextMessage(validated);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); deliverNextPendingMessageAsync(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
private void deliverNextPendingMessage(final Queue<MessageId> pending) { private void deliverNextPendingMessageAsync(
final Queue<MessageId> pending) {
if (pending.isEmpty()) return; if (pending.isEmpty()) return;
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
Message m = null; deliverNextPendingMessage(pending);
ClientId c = null;
try {
boolean allDelivered = true;
Metadata meta = null;
Transaction txn = db.startTransaction(true);
try {
MessageId id = pending.poll();
byte[] raw = db.getRawMessage(txn, id);
m = parseMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId());
c = g.getClientId();
// check if a dependency is invalid
Map<MessageId, State> states =
db.getMessageDependencies(txn, id);
for (Entry<MessageId, State> d : states.entrySet()) {
if (d.getValue() == INVALID) {
throw new InvalidMessageException(
"Invalid Dependency");
}
if (d.getValue() != DELIVERED) allDelivered = false;
}
if (allDelivered)
meta = db.getMessageMetadataForValidator(txn, id);
txn.setComplete();
} finally {
if (!txn.isComplete()) txn.setComplete();
db.endTransaction(txn);
}
if (c != null && allDelivered) deliverMessage(m, c, meta);
deliverNextPendingMessage(pending);
} catch(InvalidMessageException e) {
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
markMessageInvalid(m, c);
deliverNextPendingMessage(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
private void deliverNextPendingMessage(Queue<MessageId> pending) {
try {
boolean anyInvalid = false, allDelivered = true;
Queue<MessageId> invalidate = null;
Transaction txn = db.startTransaction(false);
try {
MessageId id = pending.poll();
// Check if message is still pending
if (db.getMessageState(txn, id) == PENDING) {
// Check if dependencies are valid and delivered
Map<MessageId, State> states =
db.getMessageDependencies(txn, id);
for (Entry<MessageId, State> e : states.entrySet()) {
if (e.getValue() == INVALID) anyInvalid = true;
if (e.getValue() != DELIVERED) allDelivered = false;
}
if (anyInvalid) {
if (db.getMessageState(txn, id) != INVALID) {
invalidateMessage(txn, id);
invalidate = getDependentsToInvalidate(txn, id);
}
} else if (allDelivered) {
Message m = parseMessage(id, db.getRawMessage(txn, id));
Group g = db.getGroup(txn, m.getGroupId());
ClientId c = g.getClientId();
Metadata meta = db.getMessageMetadataForValidator(txn,
id);
if (deliverMessage(txn, m, c, meta)) {
pending.addAll(getPendingDependents(txn, id));
} else {
invalidate = getDependentsToInvalidate(txn, id);
}
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (invalidate != null) invalidateNextMessageAsync(invalidate);
deliverNextPendingMessageAsync(pending);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before delivery");
deliverNextPendingMessageAsync(pending);
} catch (NoSuchGroupException e) {
LOG.info("Group removed before delivery");
deliverNextPendingMessageAsync(pending);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
private Message parseMessage(MessageId id, byte[] raw) { private Message parseMessage(MessageId id, byte[] raw) {
if (raw.length <= MESSAGE_HEADER_LENGTH) if (raw.length <= MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -262,199 +249,168 @@ class ValidationManagerImpl implements ValidationManager, Service,
return new Message(id, new GroupId(groupId), timestamp, raw); return new Message(id, new GroupId(groupId), timestamp, raw);
} }
private void validateMessage(final Message m, final Group g) { private void validateMessageAsync(final Message m, final Group g) {
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
MessageValidator v = validators.get(g.getClientId()); validateMessage(m, g);
if (v == null) {
LOG.warning("No validator");
} else {
try {
MessageContext context = v.validateMessage(m, g);
storeMessageContext(m, g.getClientId(), context);
} catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
markMessageInvalid(m, g.getClientId());
}
}
} }
}); });
} }
private void storeMessageContext(final Message m, final ClientId c, private void validateMessage(Message m, Group g) {
MessageValidator v = validators.get(g.getClientId());
if (v == null) {
LOG.warning("No validator");
} else {
try {
MessageContext context = v.validateMessage(m, g);
storeMessageContextAsync(m, g.getClientId(), context);
} catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
Queue<MessageId> invalidate = new LinkedList<MessageId>();
invalidate.add(m.getId());
invalidateNextMessageAsync(invalidate);
}
}
}
private void storeMessageContextAsync(final Message m, final ClientId c,
final MessageContext result) { final MessageContext result) {
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { storeMessageContext(m, c, result);
State newState = null;
Metadata meta = null;
Transaction txn = db.startTransaction(false);
try {
// store dependencies
Collection<MessageId> dependencies =
result.getDependencies();
if (dependencies != null && dependencies.size() > 0) {
db.addMessageDependencies(txn, m, dependencies);
}
// check if a dependency is invalid
// and if all dependencies have been delivered
Map<MessageId, State> states =
db.getMessageDependencies(txn, m.getId());
newState = VALID;
for (Entry<MessageId, State> d : states.entrySet()) {
if (d.getValue() == INVALID) {
throw new InvalidMessageException(
"Dependency Invalid");
}
if (d.getValue() != DELIVERED) {
newState = PENDING;
LOG.info("depend. undelivered, set to PENDING");
break;
}
}
// save metadata and new message state
meta = result.getMetadata();
db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageState(txn, m, c, newState);
txn.setComplete();
} finally {
if (!txn.isComplete()) txn.setComplete();
db.endTransaction(txn);
}
// deliver message if valid
if (newState == VALID) {
deliverMessage(m, c, meta);
}
} catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
markMessageInvalid(m, c);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
private void deliverMessage(final Message m, final ClientId c, private void storeMessageContext(Message m, ClientId c,
final Metadata meta) { MessageContext result) {
try {
MessageId id = m.getId();
boolean anyInvalid = false, allDelivered = true;
Queue<MessageId> invalidate = null;
Queue<MessageId> pending = null;
Transaction txn = db.startTransaction(false);
try {
// Check if message has any dependencies
Collection<MessageId> dependencies = result.getDependencies();
if (!dependencies.isEmpty()) {
db.addMessageDependencies(txn, m, dependencies);
// Check if dependencies are valid and delivered
Map<MessageId, State> states =
db.getMessageDependencies(txn, id);
for (Entry<MessageId, State> e : states.entrySet()) {
if (e.getValue() == INVALID) anyInvalid = true;
if (e.getValue() != DELIVERED) allDelivered = false;
}
}
if (anyInvalid) {
if (db.getMessageState(txn, id) != INVALID) {
invalidateMessage(txn, id);
invalidate = getDependentsToInvalidate(txn, id);
}
} else {
Metadata meta = result.getMetadata();
db.mergeMessageMetadata(txn, id, meta);
if (allDelivered) {
if (deliverMessage(txn, m, c, meta)) {
pending = getPendingDependents(txn, id);
} else {
invalidate = getDependentsToInvalidate(txn, id);
}
} else {
db.setMessageState(txn, id, PENDING);
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (invalidate != null) invalidateNextMessageAsync(invalidate);
if (pending != null) deliverNextPendingMessageAsync(pending);
} catch (NoSuchMessageException e) {
LOG.info("Message removed during validation");
} catch (NoSuchGroupException e) {
LOG.info("Group removed during validation");
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private boolean deliverMessage(Transaction txn, Message m, ClientId c,
Metadata meta) throws DbException {
IncomingMessageHook hook = hooks.get(c);
if (hook == null) throw new DbException();
hook.incomingMessage(txn, m, meta);
// TODO: Find a better way for clients to signal validity, #643
if (db.getRawMessage(txn, m.getId()) == null) {
db.setMessageState(txn, m.getId(), INVALID);
return false;
} else {
db.setMessageState(txn, m.getId(), DELIVERED);
return true;
}
}
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
throws DbException {
Queue<MessageId> pending = new LinkedList<MessageId>();
Map<MessageId, State> states = db.getMessageDependents(txn, m);
for (Entry<MessageId, State> e : states.entrySet()) {
if (e.getValue() == PENDING) pending.add(e.getKey());
}
return pending;
}
private void invalidateNextMessageAsync(final Queue<MessageId> invalidate) {
if (invalidate.isEmpty()) return;
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { invalidateNextMessage(invalidate);
Queue<MessageId> pending = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(false);
try {
IncomingMessageHook hook = hooks.get(c);
if (hook != null)
hook.incomingMessage(txn, m, meta);
// check if message was deleted by client
if (db.getRawMessage(txn, m.getId()) == null) {
throw new InvalidMessageException(
"Deleted by Client");
}
db.setMessageState(txn, m, c, DELIVERED);
// deliver pending dependents
Map<MessageId, State> dependents =
db.getMessageDependents(txn, m.getId());
for (Entry<MessageId, State> i : dependents
.entrySet()) {
if (i.getValue() != PENDING) continue;
// check that all dependencies are delivered
Map<MessageId, State> dependencies =
db.getMessageDependencies(txn, i.getKey());
for (Entry<MessageId, State> j : dependencies
.entrySet()) {
if (j.getValue() != DELIVERED) return;
}
pending.add(i.getKey());
}
txn.setComplete();
} finally {
if (!txn.isComplete()) txn.setComplete();
db.endTransaction(txn);
}
deliverNextMessage(pending);
} catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
markMessageInvalid(m, c);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
private void markMessageInvalid(final Message m, final ClientId c) { private void invalidateNextMessage(Queue<MessageId> invalidate) {
dbExecutor.execute(new Runnable() { try {
@Override Transaction txn = db.startTransaction(false);
public void run() { try {
try { MessageId id = invalidate.poll();
Queue<MessageId> invalid = new LinkedList<MessageId>(); if (db.getMessageState(txn, id) != INVALID) {
Transaction txn = db.startTransaction(false); invalidateMessage(txn, id);
try { invalidate.addAll(getDependentsToInvalidate(txn, id));
Map<MessageId, State> dependents =
db.getMessageDependents(txn, m.getId());
db.setMessageState(txn, m, c, INVALID);
db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId());
// recursively invalidate all messages that depend on m
// TODO check that cycles are properly taken care of
for (Entry<MessageId, State> i : dependents
.entrySet()) {
if (i.getValue() != INVALID) {
invalid.add(i.getKey());
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
markNextMessageInvalid(invalid);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
}); invalidateNextMessageAsync(invalidate);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before invalidation");
invalidateNextMessageAsync(invalidate);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
private void markNextMessageInvalid(final Queue<MessageId> invalid) { private void invalidateMessage(Transaction txn, MessageId m)
if (invalid.isEmpty()) return; throws DbException {
dbExecutor.execute(new Runnable() { db.setMessageState(txn, m, INVALID);
@Override db.deleteMessage(txn, m);
public void run() { db.deleteMessageMetadata(txn, m);
try { }
Message m = null;
Group g = null; private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
Transaction txn = db.startTransaction(true); MessageId m) throws DbException {
try { Queue<MessageId> invalidate = new LinkedList<MessageId>();
MessageId id = invalid.poll(); Map<MessageId, State> states = db.getMessageDependents(txn, m);
byte[] raw = db.getRawMessage(txn, id); for (Entry<MessageId, State> e : states.entrySet()) {
m = parseMessage(id, raw); if (e.getValue() != INVALID) invalidate.add(e.getKey());
g = db.getGroup(txn, m.getGroupId()); }
txn.setComplete(); return invalidate;
} finally {
db.endTransaction(txn);
}
if (g != null) markMessageInvalid(m, g.getClientId());
markNextMessageInvalid(invalid);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
} }
@Override @Override
@@ -462,31 +418,35 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (e instanceof MessageAddedEvent) { if (e instanceof MessageAddedEvent) {
// Validate the message if it wasn't created locally // Validate the message if it wasn't created locally
MessageAddedEvent m = (MessageAddedEvent) e; MessageAddedEvent m = (MessageAddedEvent) e;
if (m.getContactId() != null) loadGroupAndValidate(m.getMessage()); if (m.getContactId() != null)
loadGroupAndValidateAsync(m.getMessage());
} }
} }
private void loadGroupAndValidate(final Message m) { private void loadGroupAndValidateAsync(final Message m) {
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { loadGroupAndValidate(m);
Group g;
Transaction txn = db.startTransaction(true);
try {
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) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
private void loadGroupAndValidate(final Message m) {
try {
Group g;
Transaction txn = db.startTransaction(true);
try {
g = db.getGroup(txn, m.getGroupId());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
validateMessageAsync(m, g);
} catch (NoSuchGroupException e) {
LOG.info("Group removed before validation");
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
} }

View File

@@ -292,7 +292,7 @@ public class BlogManagerImplTest extends BriarTestCase {
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn)); will(returnValue(txn));
oneOf(clientHelper) oneOf(clientHelper)
.addLocalMessage(txn, message, CLIENT_ID, meta, true); .addLocalMessage(txn, message, meta, true);
oneOf(identityManager) oneOf(identityManager)
.getAuthorStatus(txn, blog1.getAuthor().getId()); .getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED)); will(returnValue(VERIFIED));

View File

@@ -16,11 +16,11 @@ import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator; import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -77,7 +77,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
body); body);
will(new CreateMessageAction()); will(new CreateMessageAction());
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)), oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
with(clientId), with(messageMetadata), with(true)); with(messageMetadata), with(true));
// Second message: queue state exists // Second message: queue state exists
oneOf(db).getGroupMetadata(txn, groupId); oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(groupMetadata1)); will(returnValue(groupMetadata1));
@@ -91,7 +91,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
body); body);
will(new CreateMessageAction()); will(new CreateMessageAction());
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)), oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
with(clientId), with(messageMetadata), with(true)); with(messageMetadata), with(true));
}}); }});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,

View File

@@ -27,9 +27,9 @@ import org.briarproject.api.event.LocalAuthorRemovedEvent;
import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageRequestedEvent;
import org.briarproject.api.event.MessageSharedEvent; import org.briarproject.api.event.MessageSharedEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToAckEvent;
import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.MessageToRequestEvent;
import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.event.SettingsUpdatedEvent;
@@ -62,7 +62,6 @@ import java.util.Map;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; 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.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -241,7 +240,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, clientId, metadata, true); db.addLocalMessage(transaction, message, metadata, true);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
@@ -284,7 +283,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, clientId, metadata, true); db.addLocalMessage(transaction, message, metadata, true);
transaction.setComplete(); transaction.setComplete();
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
@@ -677,11 +676,11 @@ public class DatabaseComponentImplTest extends BriarTestCase {
final EventBus eventBus = context.mock(EventBus.class); final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not) // Check whether the message is in the DB (which it's not)
exactly(10).of(database).startTransaction(); exactly(11).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(10).of(database).containsMessage(txn, messageId); exactly(11).of(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
exactly(10).of(database).abortTransaction(txn); exactly(11).of(database).abortTransaction(txn);
// This is needed for getMessageStatus() to proceed // This is needed for getMessageStatus() to proceed
exactly(1).of(database).containsContact(txn, contactId); exactly(1).of(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
@@ -729,6 +728,16 @@ public class DatabaseComponentImplTest extends BriarTestCase {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
transaction = db.startTransaction(false);
try {
db.getMessageState(transaction, messageId);
fail();
} catch (NoSuchMessageException expected) {
// Expected
} finally {
db.endTransaction(transaction);
}
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.getMessageStatus(transaction, contactId, messageId); db.getMessageStatus(transaction, contactId, messageId);
@@ -761,7 +770,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.setMessageState(transaction, message, clientId, VALID); db.setMessageState(transaction, messageId, DELIVERED);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
@@ -1652,8 +1661,10 @@ public class DatabaseComponentImplTest extends BriarTestCase {
// addMessageDependencies() // addMessageDependencies()
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).addMessageDependency(txn, messageId, messageId1); oneOf(database).addMessageDependency(txn, groupId, messageId,
oneOf(database).addMessageDependency(txn, messageId, messageId2); messageId1);
oneOf(database).addMessageDependency(txn, groupId, messageId,
messageId2);
// getMessageDependencies() // getMessageDependencies()
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));
@@ -1664,7 +1675,8 @@ public class DatabaseComponentImplTest extends BriarTestCase {
oneOf(database).getMessageDependents(txn, messageId); oneOf(database).getMessageDependents(txn, messageId);
// broadcast for message added event // broadcast for message added event
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(MessageStateChangedEvent.class))); oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
@@ -1678,7 +1690,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
assertFalse(db.open()); assertFalse(db.open());
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, clientId, metadata, true); db.addLocalMessage(transaction, message, metadata, true);
Collection<MessageId> dependencies = new ArrayList<>(2); Collection<MessageId> dependencies = new ArrayList<>(2);
dependencies.add(messageId1); dependencies.add(messageId1);
dependencies.add(messageId2); dependencies.add(messageId2);

View File

@@ -50,7 +50,6 @@ 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; 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.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.State.VALID;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -67,6 +66,7 @@ public class H2DatabaseTest extends BriarTestCase {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
private final GroupId groupId; private final GroupId groupId;
private final ClientId clientId;
private final Group group; private final Group group;
private final Author author; private final Author author;
private final AuthorId localAuthorId; private final AuthorId localAuthorId;
@@ -81,7 +81,7 @@ public class H2DatabaseTest extends BriarTestCase {
public H2DatabaseTest() throws Exception { public H2DatabaseTest() throws Exception {
groupId = new GroupId(TestUtils.getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
ClientId clientId = new ClientId(TestUtils.getRandomId()); clientId = new ClientId(TestUtils.getRandomId());
byte[] descriptor = new byte[0]; byte[] descriptor = new byte[0];
group = new Group(groupId, clientId, descriptor); group = new Group(groupId, clientId, descriptor);
AuthorId authorId = new AuthorId(TestUtils.getRandomId()); AuthorId authorId = new AuthorId(TestUtils.getRandomId());
@@ -239,16 +239,7 @@ public class H2DatabaseTest extends BriarTestCase {
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertTrue(ids.isEmpty()); 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 // 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); db.setMessageState(txn, messageId, PENDING);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
@@ -1019,13 +1010,6 @@ public class H2DatabaseTest extends BriarTestCase {
map = db.getMessageMetadata(txn, groupId); map = db.getMessageMetadata(txn, groupId);
assertTrue(map.isEmpty()); 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 // No metadata for pending messages
db.setMessageState(txn, messageId, PENDING); db.setMessageState(txn, messageId, PENDING);
retrieved = db.getMessageMetadata(txn, messageId); retrieved = db.getMessageMetadata(txn, messageId);
@@ -1033,7 +1017,7 @@ public class H2DatabaseTest extends BriarTestCase {
map = db.getMessageMetadata(txn, groupId); map = db.getMessageMetadata(txn, groupId);
assertTrue(map.isEmpty()); assertTrue(map.isEmpty());
// validator gets also metadata for pending messages // Validator can get metadata for pending messages
retrieved = db.getMessageMetadataForValidator(txn, messageId); retrieved = db.getMessageMetadataForValidator(txn, messageId);
assertFalse(retrieved.isEmpty()); assertFalse(retrieved.isEmpty());
@@ -1198,12 +1182,6 @@ public class H2DatabaseTest extends BriarTestCase {
all = db.getMessageMetadata(txn, groupId, query); all = db.getMessageMetadata(txn, groupId, query);
assertTrue(all.isEmpty()); 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 // No metadata for pending messages
db.setMessageState(txn, messageId, PENDING); db.setMessageState(txn, messageId, PENDING);
db.setMessageState(txn, messageId1, PENDING); db.setMessageState(txn, messageId1, PENDING);
@@ -1217,149 +1195,139 @@ public class H2DatabaseTest extends BriarTestCase {
@Test @Test
public void testMessageDependencies() throws Exception { public void testMessageDependencies() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
Message message1 = new Message(messageId1, groupId, timestamp, raw);
Message message2 = new Message(messageId2, groupId, timestamp, raw);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a group and a message // Add a group and some messages
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, VALID, true); db.addMessage(txn, message, PENDING, true);
db.addMessage(txn, message1, DELIVERED, true);
// Create more messages db.addMessage(txn, message2, INVALID, true);
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 // Add dependencies
db.addMessageDependency(txn, messageId, mId1); db.addMessageDependency(txn, groupId, messageId, messageId1);
db.addMessageDependency(txn, messageId, mId2); db.addMessageDependency(txn, groupId, messageId, messageId2);
db.addMessageDependency(txn, mId1, dId1); db.addMessageDependency(txn, groupId, messageId1, messageId3);
db.addMessageDependency(txn, mId2, dId2); db.addMessageDependency(txn, groupId, messageId2, messageId4);
Map<MessageId, State> dependencies; Map<MessageId, State> dependencies;
// Retrieve dependencies for root // Retrieve dependencies for root
dependencies = db.getMessageDependencies(txn, messageId); dependencies = db.getMessageDependencies(txn, messageId);
assertEquals(2, dependencies.size()); assertEquals(2, dependencies.size());
assertEquals(VALID, dependencies.get(mId1)); assertEquals(DELIVERED, dependencies.get(messageId1));
assertEquals(INVALID, dependencies.get(mId2)); assertEquals(INVALID, dependencies.get(messageId2));
// Retrieve dependencies for m1 // Retrieve dependencies for message 1
dependencies = db.getMessageDependencies(txn, mId1); dependencies = db.getMessageDependencies(txn, messageId1);
assertEquals(1, dependencies.size()); assertEquals(1, dependencies.size());
assertEquals(UNKNOWN, dependencies.get(dId1)); assertEquals(UNKNOWN, dependencies.get(messageId3)); // Missing
// Retrieve dependencies for m2 // Retrieve dependencies for message 2
dependencies = db.getMessageDependencies(txn, mId2); dependencies = db.getMessageDependencies(txn, messageId2);
assertEquals(1, dependencies.size()); assertEquals(1, dependencies.size());
assertEquals(UNKNOWN, dependencies.get(dId2)); assertEquals(UNKNOWN, dependencies.get(messageId4)); // Missing
// Make sure d's have no dependencies // Make sure leaves have no dependencies
dependencies = db.getMessageDependencies(txn, dId1); dependencies = db.getMessageDependencies(txn, messageId3);
assertTrue(dependencies.isEmpty()); assertEquals(0, dependencies.size());
dependencies = db.getMessageDependencies(txn, dId2); dependencies = db.getMessageDependencies(txn, messageId4);
assertTrue(dependencies.isEmpty()); assertEquals(0, dependencies.size());
Map<MessageId, State> dependents; Map<MessageId, State> dependents;
// Root message does not have dependents // Root message does not have dependents
dependents = db.getMessageDependents(txn, messageId); dependents = db.getMessageDependents(txn, messageId);
assertTrue(dependents.isEmpty()); assertEquals(0, dependents.size());
// The root message depends on both m's // Messages 1 and 2 have the root as a dependent
dependents = db.getMessageDependents(txn, mId1); dependents = db.getMessageDependents(txn, messageId1);
assertEquals(1, dependents.size()); assertEquals(1, dependents.size());
assertEquals(VALID, dependents.get(messageId)); assertEquals(PENDING, dependents.get(messageId));
dependents = db.getMessageDependents(txn, mId2); dependents = db.getMessageDependents(txn, messageId2);
assertEquals(1, dependents.size()); assertEquals(1, dependents.size());
assertEquals(VALID, dependents.get(messageId)); assertEquals(PENDING, dependents.get(messageId));
// Both m's depend on the d's // Message 3 has message 1 as a dependent
dependents = db.getMessageDependents(txn, dId1); dependents = db.getMessageDependents(txn, messageId3);
assertEquals(1, dependents.size()); assertEquals(1, dependents.size());
assertEquals(VALID, dependents.get(mId1)); assertEquals(DELIVERED, dependents.get(messageId1));
dependents = db.getMessageDependents(txn, dId2);
// Message 4 has message 2 as a dependent
dependents = db.getMessageDependents(txn, messageId4);
assertEquals(1, dependents.size()); assertEquals(1, dependents.size());
assertEquals(INVALID, dependents.get(mId2)); assertEquals(INVALID, dependents.get(messageId2));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@Test @Test
public void testMessageDependenciesInSameGroup() throws Exception { public void testMessageDependenciesAcrossGroups() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, PENDING, true);
// Add a second group // Add a second group
GroupId groupId1 = new GroupId(TestUtils.getRandomId()); GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Group group1 = new Group(groupId1, group.getClientId(), Group group1 = new Group(groupId1, clientId,
TestUtils.getRandomBytes(42)); TestUtils.getRandomBytes(42));
db.addGroup(txn, group1); db.addGroup(txn, group1);
// Add a message to the second group // Add a message to the second group
MessageId mId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message m1 = new Message(mId1, groupId1, timestamp, raw); Message message1 = new Message(messageId1, groupId1, timestamp, raw);
db.addMessage(txn, m1, DELIVERED, true); db.addMessage(txn, message1, DELIVERED, true);
// Create a fake dependency as well // Create an ID for a missing message
MessageId mId2 = new MessageId(TestUtils.getRandomId()); MessageId messageId2 = new MessageId(TestUtils.getRandomId());
// Create and add a real and proper dependency // Add another message to the first group
MessageId mId3 = new MessageId(TestUtils.getRandomId()); MessageId messageId3 = new MessageId(TestUtils.getRandomId());
Message m3 = new Message(mId3, groupId, timestamp, raw); Message message3 = new Message(messageId3, groupId, timestamp, raw);
db.addMessage(txn, m3, PENDING, true); db.addMessage(txn, message3, DELIVERED, true);
// Add dependencies // Add dependencies between the messages
db.addMessageDependency(txn, messageId, mId1); db.addMessageDependency(txn, groupId, messageId, messageId1);
db.addMessageDependency(txn, messageId, mId2); db.addMessageDependency(txn, groupId, messageId, messageId2);
db.addMessageDependency(txn, messageId, mId3); db.addMessageDependency(txn, groupId, messageId, messageId3);
// Return invalid dependencies for delivered message m1 // Retrieve the dependencies for the root
Map<MessageId, State> dependencies; Map<MessageId, State> dependencies;
dependencies = db.getMessageDependencies(txn, messageId); 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 // The cross-group dependency should have state INVALID
db.setMessageState(txn, mId1, VALID); assertEquals(INVALID, dependencies.get(messageId1));
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 // The missing dependency should have state UNKNOWN
db.setMessageState(txn, mId1, PENDING); assertEquals(UNKNOWN, dependencies.get(messageId2));
dependencies = db.getMessageDependencies(txn, messageId);
assertEquals(INVALID, dependencies.get(mId1)); // The valid dependency should have its real state
assertEquals(UNKNOWN, dependencies.get(mId2)); assertEquals(DELIVERED, dependencies.get(messageId3));
assertEquals(PENDING, dependencies.get(mId3));
// Retrieve the dependents for the message in the second group
Map<MessageId, State> dependents;
dependents = db.getMessageDependents(txn, messageId1);
// The cross-group dependent should have its real state
assertEquals(PENDING, dependents.get(messageId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@Test @Test
public void testGetMessagesForValidationAndDelivery() throws Exception { public void testGetPendingMessagesForDelivery() 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 mId1 = new MessageId(TestUtils.getRandomId());
MessageId mId2 = new MessageId(TestUtils.getRandomId()); MessageId mId2 = new MessageId(TestUtils.getRandomId());
MessageId mId3 = new MessageId(TestUtils.getRandomId()); MessageId mId3 = new MessageId(TestUtils.getRandomId());
@@ -1369,7 +1337,11 @@ public class H2DatabaseTest extends BriarTestCase {
Message m3 = new Message(mId3, groupId, timestamp, raw); Message m3 = new Message(mId3, groupId, timestamp, raw);
Message m4 = new Message(mId4, groupId, timestamp, raw); Message m4 = new Message(mId4, groupId, timestamp, raw);
// Add new messages with different states Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a group and some messages with different states
db.addGroup(txn, group);
db.addMessage(txn, m1, UNKNOWN, true); db.addMessage(txn, m1, UNKNOWN, true);
db.addMessage(txn, m2, INVALID, true); db.addMessage(txn, m2, INVALID, true);
db.addMessage(txn, m3, PENDING, true); db.addMessage(txn, m3, PENDING, true);
@@ -1378,17 +1350,12 @@ public class H2DatabaseTest extends BriarTestCase {
Collection<MessageId> result; Collection<MessageId> result;
// Retrieve messages to be validated // Retrieve messages to be validated
result = db.getMessagesToValidate(txn, group.getClientId()); result = db.getMessagesToValidate(txn, clientId);
assertEquals(1, result.size()); assertEquals(1, result.size());
assertTrue(result.contains(mId1)); 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 // Retrieve pending messages
result = db.getPendingMessages(txn, group.getClientId()); result = db.getPendingMessages(txn, clientId);
assertEquals(1, result.size()); assertEquals(1, result.size());
assertTrue(result.contains(mId3)); assertTrue(result.contains(mId3));
@@ -1607,6 +1574,29 @@ public class H2DatabaseTest extends BriarTestCase {
db.close(); db.close();
} }
@Test
public void testSetMessageState() 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, UNKNOWN, false);
// Walk the message through the validation and delivery states
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
db.setMessageState(txn, messageId, INVALID);
assertEquals(INVALID, db.getMessageState(txn, messageId));
db.setMessageState(txn, messageId, PENDING);
assertEquals(PENDING, db.getMessageState(txn, messageId));
db.setMessageState(txn, messageId, DELIVERED);
assertEquals(DELIVERED, db.getMessageState(txn, messageId));
db.commitTransaction(txn);
db.close();
}
@Test @Test
public void testExceptionHandling() throws Exception { public void testExceptionHandling() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);

View File

@@ -279,8 +279,7 @@ public class IntroduceeManagerTest extends BriarTestCase {
// store session state // store session state
oneOf(clientHelper) oneOf(clientHelper)
.addLocalMessage(txn, localStateMessage, clientId, state, .addLocalMessage(txn, localStateMessage, state, false);
false);
}}); }});
BdfDictionary result = introduceeManager.initialize(txn, groupId, msg); BdfDictionary result = introduceeManager.initialize(txn, groupId, msg);

View File

@@ -167,8 +167,7 @@ public class IntroducerManagerTest extends BriarTestCase {
oneOf(introductionGroupFactory) oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2); .createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2)); will(returnValue(introductionGroup2));
oneOf(clientHelper).addLocalMessage(txn, msg, getClientId(), state, oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
false);
// send message // send message
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2); oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);

View File

@@ -26,11 +26,9 @@ import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -38,7 +36,7 @@ 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.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; 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.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.State.VALID; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ValidationManagerImplTest extends BriarTestCase { public class ValidationManagerImplTest extends BriarTestCase {
@@ -58,21 +56,17 @@ public class ValidationManagerImplTest extends BriarTestCase {
raw); raw);
private final Message message2 = new Message(messageId2, groupId, timestamp, private final Message message2 = new Message(messageId2, groupId, timestamp,
raw); raw);
private final Metadata metadata = new Metadata(); private final Metadata metadata = new Metadata();
private final MessageContext validResult = new MessageContext(metadata); private final MessageContext validResult = new MessageContext(metadata);
private final ContactId contactId = new ContactId(234); private final ContactId contactId = new ContactId(234);
private final Collection<MessageId> dependencies = new ArrayList<>();
private final MessageContext validResultWithDependencies = private final MessageContext validResultWithDependencies =
new MessageContext(metadata, dependencies); new MessageContext(metadata, Collections.singletonList(messageId1));
private final Map<MessageId, State> states = new HashMap<>();
public ValidationManagerImplTest() { public ValidationManagerImplTest() {
// Encode the messages // Encode the messages
System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH); System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
dependencies.add(messageId1);
states.put(messageId1, INVALID);
} }
@Test @Test
@@ -84,11 +78,10 @@ public class ValidationManagerImplTest extends BriarTestCase {
final MessageValidator validator = context.mock(MessageValidator.class); final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook = final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, false); final Transaction txn2 = new Transaction(null, false);
final Transaction txn2b = new Transaction(null, false); final Transaction txn3 = new Transaction(null, true);
final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, false); final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, true); final Transaction txn5 = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -112,21 +105,16 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result for the first message // Store the validation result for the first message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(db).getMessageDependencies(txn2, messageId);
oneOf(db).mergeMessageMetadata(txn2, messageId, metadata); oneOf(db).mergeMessageMetadata(txn2, messageId, metadata);
oneOf(db).setMessageState(txn2, message, clientId, VALID); // Deliver the first message
oneOf(db).endTransaction(txn2); oneOf(hook).incomingMessage(txn2, message, metadata);
// Async delivery oneOf(db).getRawMessage(txn2, messageId);
oneOf(db).startTransaction(false);
will(returnValue(txn2b));
// Call the hook for the first message
oneOf(hook).incomingMessage(txn2b, message, metadata);
oneOf(db).getRawMessage(txn2b, messageId);
will(returnValue(raw)); will(returnValue(raw));
oneOf(db).setMessageState(txn2b, message, clientId, DELIVERED); oneOf(db).setMessageState(txn2, messageId, DELIVERED);
oneOf(db).getMessageDependents(txn2b, messageId); // Get any pending dependents
oneOf(db).getMessageDependents(txn2, messageId);
will(returnValue(Collections.emptyMap())); will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn2b); oneOf(db).endTransaction(txn2);
// Load the second raw message and group // Load the second raw message and group
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn3)); will(returnValue(txn3));
@@ -141,101 +129,20 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result for the second message // Store the validation result for the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).setMessageState(txn4, message1, clientId, INVALID); oneOf(db).getMessageState(txn4, messageId1);
// Recursively invalidate dependents will(returnValue(UNKNOWN));
oneOf(db).getMessageDependents(txn4, messageId1); oneOf(db).setMessageState(txn4, messageId1, INVALID);
oneOf(db).deleteMessage(txn4, messageId1); oneOf(db).deleteMessage(txn4, messageId1);
oneOf(db).deleteMessageMetadata(txn4, messageId1); oneOf(db).deleteMessageMetadata(txn4, messageId1);
// Recursively invalidate any dependents
oneOf(db).getMessageDependents(txn4, messageId1);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
// Get other messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn5)); will(returnValue(txn5));
oneOf(db).getMessagesToDeliver(txn5, clientId);
oneOf(db).getPendingMessages(txn5, clientId); oneOf(db).getPendingMessages(txn5, clientId);
oneOf(db).endTransaction(txn5); will(returnValue(Collections.emptyList()));
}});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.startService();
context.assertIsSatisfied();
}
@Test
public void testMessagesAreDeliveredAtStartup() 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 IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true);
final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, true);
final Transaction txn5 = new Transaction(null, false);
states.put(messageId1, PENDING);
context.checking(new Expectations() {{
// Get messages to validate
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId);
oneOf(db).endTransaction(txn);
// Get IDs of messages to deliver
oneOf(db).startTransaction(true);
will(returnValue(txn1));
oneOf(db).getMessagesToDeliver(txn1, clientId);
will(returnValue(Collections.singletonList(messageId)));
oneOf(db).getPendingMessages(txn1, clientId);
oneOf(db).endTransaction(txn1);
// Get message and its metadata to deliver
oneOf(db).startTransaction(true);
will(returnValue(txn2));
oneOf(db).getRawMessage(txn2, messageId);
will(returnValue(message.getRaw()));
oneOf(db).getGroup(txn2, message.getGroupId());
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn2, messageId);
will(returnValue(metadata));
oneOf(db).endTransaction(txn2);
// Deliver message in a new transaction
oneOf(db).startTransaction(false);
will(returnValue(txn3));
oneOf(db).setMessageState(txn3, message, clientId, DELIVERED);
oneOf(hook).incomingMessage(txn3, message, metadata);
oneOf(db).getRawMessage(txn3, messageId);
will(returnValue(message.getRaw()));
// Try to also deliver pending dependents
oneOf(db).getMessageDependents(txn3, messageId);
will(returnValue(states));
oneOf(db).getMessageDependencies(txn3, messageId1);
will(returnValue(Collections.singletonMap(messageId2, DELIVERED)));
oneOf(db).endTransaction(txn3);
// Get the dependent to deliver
oneOf(db).startTransaction(true);
will(returnValue(txn4));
oneOf(db).getRawMessage(txn4, messageId1);
will(returnValue(message1.getRaw()));
oneOf(db).getGroup(txn4, message.getGroupId());
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn4, messageId1);
will(returnValue(metadata));
oneOf(db).endTransaction(txn4);
// Deliver the dependent in a new transaction
oneOf(db).startTransaction(false);
will(returnValue(txn5));
oneOf(db).setMessageState(txn5, message1, clientId, DELIVERED);
oneOf(hook).incomingMessage(txn5, message1, metadata);
oneOf(db).getRawMessage(txn5, messageId1);
will(returnValue(message1.getRaw()));
oneOf(db).getMessageDependents(txn5, messageId1);
oneOf(db).endTransaction(txn5); oneOf(db).endTransaction(txn5);
}}); }});
@@ -266,7 +173,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, true); final Transaction txn1 = new Transaction(null, true);
final Transaction txn2 = new Transaction(null, true); final Transaction txn2 = new Transaction(null, false);
final Transaction txn3 = new Transaction(null, false); final Transaction txn3 = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -274,33 +181,59 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
// Get IDs of messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getMessagesToDeliver(txn1, clientId);
oneOf(db).getPendingMessages(txn1, clientId); oneOf(db).getPendingMessages(txn1, clientId);
will(returnValue(Collections.singletonList(messageId))); will(returnValue(Collections.singletonList(messageId)));
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Get message and its metadata to deliver // Check whether the message is ready to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(db).getRawMessage(txn2, messageId); oneOf(db).getMessageState(txn2, messageId);
will(returnValue(message.getRaw())); will(returnValue(PENDING));
oneOf(db).getGroup(txn2, message.getGroupId());
will(returnValue(group));
oneOf(db).getMessageDependencies(txn2, messageId); oneOf(db).getMessageDependencies(txn2, messageId);
will(returnValue(Collections.singletonMap(messageId1, DELIVERED))); will(returnValue(Collections.singletonMap(messageId1, DELIVERED)));
// Get the message and its metadata to deliver
oneOf(db).getRawMessage(txn2, messageId);
will(returnValue(raw));
oneOf(db).getGroup(txn2, groupId);
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn2, messageId); oneOf(db).getMessageMetadataForValidator(txn2, messageId);
will(returnValue(new Metadata()));
// Deliver the message
oneOf(hook).incomingMessage(txn2, message, metadata);
oneOf(db).getRawMessage(txn2, messageId);
will(returnValue(raw));
oneOf(db).setMessageState(txn2, messageId, DELIVERED);
// Get any pending dependents
oneOf(db).getMessageDependents(txn2, messageId);
will(returnValue(Collections.singletonMap(messageId2, PENDING)));
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
// Deliver the pending message // Check whether the dependent is ready to deliver
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn3)); will(returnValue(txn3));
oneOf(db).setMessageState(txn3, message, clientId, DELIVERED); oneOf(db).getMessageState(txn3, messageId2);
oneOf(hook).incomingMessage(txn3, message, metadata); will(returnValue(PENDING));
oneOf(db).getRawMessage(txn3, messageId); oneOf(db).getMessageDependencies(txn3, messageId2);
will(returnValue(message.getRaw())); will(returnValue(Collections.singletonMap(messageId1, DELIVERED)));
oneOf(db).getMessageDependents(txn3, messageId); // Get the dependent and its metadata to deliver
oneOf(db).getRawMessage(txn3, messageId2);
will(returnValue(raw));
oneOf(db).getGroup(txn3, groupId);
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn3, messageId2);
will(returnValue(metadata));
// Deliver the dependent
oneOf(hook).incomingMessage(txn3, message2, metadata);
oneOf(db).getRawMessage(txn3, messageId2);
will(returnValue(raw));
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
// Get any pending dependents
oneOf(db).getMessageDependents(txn3, messageId2);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
}}); }});
@@ -357,20 +290,23 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Validate the second message: invalid // Validate the second message: invalid
oneOf(validator).validateMessage(message1, group); oneOf(validator).validateMessage(message1, group);
will(throwException(new InvalidMessageException())); will(throwException(new InvalidMessageException()));
// Store the validation result for the second message // Invalidate the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn3)); will(returnValue(txn3));
oneOf(db).setMessageState(txn3, message1, clientId, INVALID); oneOf(db).getMessageState(txn3, messageId1);
// recursively invalidate dependents will(returnValue(UNKNOWN));
oneOf(db).getMessageDependents(txn3, messageId1); oneOf(db).setMessageState(txn3, messageId1, INVALID);
oneOf(db).deleteMessage(txn3, messageId1); oneOf(db).deleteMessage(txn3, messageId1);
oneOf(db).deleteMessageMetadata(txn3, messageId1); oneOf(db).deleteMessageMetadata(txn3, messageId1);
// Recursively invalidate dependents
oneOf(db).getMessageDependents(txn3, messageId1);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
// Get other messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).getMessagesToDeliver(txn4, clientId);
oneOf(db).getPendingMessages(txn4, clientId); oneOf(db).getPendingMessages(txn4, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
}}); }});
@@ -383,7 +319,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
assertTrue(txn.isComplete()); assertTrue(txn.isComplete());
assertTrue(txn1.isComplete()); assertFalse(txn1.isComplete()); // Aborted due to NoSuchMessageException
assertTrue(txn2.isComplete()); assertTrue(txn2.isComplete());
assertTrue(txn3.isComplete()); assertTrue(txn3.isComplete());
assertTrue(txn4.isComplete()); assertTrue(txn4.isComplete());
@@ -434,17 +370,20 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result for the second message // Store the validation result for the second message
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn3)); will(returnValue(txn3));
oneOf(db).setMessageState(txn3, message1, clientId, INVALID); oneOf(db).getMessageState(txn3, messageId1);
// recursively invalidate dependents will(returnValue(UNKNOWN));
oneOf(db).getMessageDependents(txn3, messageId1); oneOf(db).setMessageState(txn3, messageId1, INVALID);
oneOf(db).deleteMessage(txn3, messageId1); oneOf(db).deleteMessage(txn3, messageId1);
oneOf(db).deleteMessageMetadata(txn3, messageId1); oneOf(db).deleteMessageMetadata(txn3, messageId1);
// Recursively invalidate dependents
oneOf(db).getMessageDependents(txn3, messageId1);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
// Get other messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).getMessagesToDeliver(txn4, clientId);
oneOf(db).getPendingMessages(txn4, clientId); oneOf(db).getPendingMessages(txn4, clientId);
will(returnValue(Collections.emptyList()));
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
}}); }});
@@ -457,7 +396,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
assertTrue(txn.isComplete()); assertTrue(txn.isComplete());
assertTrue(txn1.isComplete()); assertFalse(txn1.isComplete()); // Aborted due to NoSuchGroupException
assertTrue(txn2.isComplete()); assertTrue(txn2.isComplete());
assertTrue(txn3.isComplete()); assertTrue(txn3.isComplete());
assertTrue(txn4.isComplete()); assertTrue(txn4.isComplete());
@@ -474,7 +413,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Load the group // Load the group
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -488,20 +426,16 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result // Store the validation result
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getMessageDependencies(txn1, messageId);
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, VALID); // Deliver the message
oneOf(db).endTransaction(txn1); oneOf(hook).incomingMessage(txn1, message, metadata);
// async delivery oneOf(db).getRawMessage(txn1, messageId);
oneOf(db).startTransaction(false);
will(returnValue(txn2));
// Call the hook
oneOf(hook).incomingMessage(txn2, message, metadata);
oneOf(db).getRawMessage(txn2, messageId);
will(returnValue(raw)); will(returnValue(raw));
oneOf(db).setMessageState(txn2, message, clientId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
oneOf(db).getMessageDependents(txn2, messageId); // Get any pending dependents
oneOf(db).endTransaction(txn2); oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn1);
}}); }});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
@@ -514,7 +448,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
assertTrue(txn.isComplete()); assertTrue(txn.isComplete());
assertTrue(txn1.isComplete()); assertTrue(txn1.isComplete());
assertTrue(txn2.isComplete());
} }
@Test @Test
@@ -537,10 +470,9 @@ public class ValidationManagerImplTest extends BriarTestCase {
} }
@Test @Test
public void testMessagesWithNonDeliveredDependenciesArePending() public void testMessagesWithUndeliveredDependenciesArePending()
throws Exception { throws Exception {
states.put(messageId1, UNKNOWN);
Mockery context = new Mockery(); Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class); final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor(); final Executor dbExecutor = new ImmediateExecutor();
@@ -566,9 +498,9 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).addMessageDependencies(txn1, message, oneOf(db).addMessageDependencies(txn1, message,
validResultWithDependencies.getDependencies()); validResultWithDependencies.getDependencies());
oneOf(db).getMessageDependencies(txn1, messageId); oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(states)); will(returnValue(Collections.singletonMap(messageId1, UNKNOWN)));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, PENDING); oneOf(db).setMessageState(txn1, messageId, PENDING);
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
}}); }});
@@ -587,8 +519,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
@Test @Test
public void testMessagesWithDeliveredDependenciesGetDelivered() public void testMessagesWithDeliveredDependenciesGetDelivered()
throws Exception { throws Exception {
states.put(messageId1, DELIVERED);
Mockery context = new Mockery(); Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class); final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Executor dbExecutor = new ImmediateExecutor(); final Executor dbExecutor = new ImmediateExecutor();
@@ -598,7 +528,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Load the group // Load the group
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -615,20 +544,17 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).addMessageDependencies(txn1, message, oneOf(db).addMessageDependencies(txn1, message,
validResultWithDependencies.getDependencies()); validResultWithDependencies.getDependencies());
oneOf(db).getMessageDependencies(txn1, messageId); oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(states)); will(returnValue(Collections.singletonMap(messageId1, DELIVERED)));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, VALID); // Deliver the message
oneOf(db).endTransaction(txn1); oneOf(hook).incomingMessage(txn1, message, metadata);
// async delivery oneOf(db).getRawMessage(txn1, messageId);
oneOf(db).startTransaction(false);
will(returnValue(txn2));
// Call the hook
oneOf(hook).incomingMessage(txn2, message, metadata);
oneOf(db).getRawMessage(txn2, messageId);
will(returnValue(raw)); will(returnValue(raw));
oneOf(db).setMessageState(txn2, message, clientId, DELIVERED); oneOf(db).setMessageState(txn1, messageId, DELIVERED);
oneOf(db).getMessageDependents(txn2, messageId); // Get any pending dependents
oneOf(db).endTransaction(txn2); oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn1);
}}); }});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
@@ -641,7 +567,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
assertTrue(txn.isComplete()); assertTrue(txn.isComplete());
assertTrue(txn1.isComplete()); assertTrue(txn1.isComplete());
assertTrue(txn2.isComplete());
} }
@Test @Test
@@ -657,8 +582,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false); final Transaction txn2 = new Transaction(null, false);
final Transaction txn3 = new Transaction(null, true);
final Transaction txn4 = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Load the group // Load the group
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -674,35 +597,142 @@ public class ValidationManagerImplTest extends BriarTestCase {
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).addMessageDependencies(txn1, message, oneOf(db).addMessageDependencies(txn1, message,
validResultWithDependencies.getDependencies()); validResultWithDependencies.getDependencies());
// Check for invalid dependencies
oneOf(db).getMessageDependencies(txn1, messageId); oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(states)); will(returnValue(Collections.singletonMap(messageId1, INVALID)));
// Invalidate message
oneOf(db).getMessageState(txn1, messageId);
will(returnValue(UNKNOWN));
oneOf(db).setMessageState(txn1, messageId, INVALID);
oneOf(db).deleteMessage(txn1, messageId);
oneOf(db).deleteMessageMetadata(txn1, messageId);
// Recursively invalidate dependents
oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(Collections.singletonMap(messageId2, UNKNOWN)));
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Invalidate message in a new transaction // Invalidate dependent in a new transaction
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(db).getMessageDependents(txn2, messageId); oneOf(db).getMessageState(txn2, messageId2);
will(returnValue(Collections.singletonMap(messageId2, UNKNOWN))); will(returnValue(UNKNOWN));
oneOf(db).setMessageState(txn2, message, clientId, INVALID); oneOf(db).setMessageState(txn2, messageId2, INVALID);
oneOf(db).deleteMessage(txn2, messageId); oneOf(db).deleteMessage(txn2, messageId2);
oneOf(db).deleteMessageMetadata(txn2, messageId); oneOf(db).deleteMessageMetadata(txn2, messageId2);
oneOf(db).endTransaction(txn2); oneOf(db).getMessageDependents(txn2, messageId2);
// Get message to invalidate in a new transaction
oneOf(db).startTransaction(true);
will(returnValue(txn3));
oneOf(db).getRawMessage(txn3, messageId2);
will(returnValue(message2.getRaw()));
oneOf(db).getGroup(txn3, message2.getGroupId());
will(returnValue(group));
oneOf(db).endTransaction(txn3);
// Invalidate dependent message in a new transaction
oneOf(db).startTransaction(false);
will(returnValue(txn4));
oneOf(db).getMessageDependents(txn4, messageId2);
will(returnValue(Collections.emptyMap())); will(returnValue(Collections.emptyMap()));
oneOf(db).setMessageState(txn4, message2, clientId, INVALID); oneOf(db).endTransaction(txn2);
oneOf(db).deleteMessage(txn4, messageId2); }});
oneOf(db).deleteMessageMetadata(txn4, messageId2);
oneOf(db).endTransaction(txn4); ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
cryptoExecutor);
vm.registerMessageValidator(clientId, validator);
vm.registerIncomingMessageHook(clientId, hook);
vm.eventOccurred(new MessageAddedEvent(message, contactId));
context.assertIsSatisfied();
assertTrue(txn.isComplete());
assertTrue(txn1.isComplete());
assertTrue(txn2.isComplete());
}
@Test
public void testRecursiveInvalidation() 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 IncomingMessageHook hook =
context.mock(IncomingMessageHook.class);
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
final Map<MessageId, State> twoDependents = new LinkedHashMap<>();
twoDependents.put(messageId1, PENDING);
twoDependents.put(messageId2, PENDING);
final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false);
final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, false);
final Transaction txn6 = new Transaction(null, false);
context.checking(new Expectations() {{
// Load the group
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getGroup(txn, groupId);
will(returnValue(group));
oneOf(db).endTransaction(txn);
// Validate the message: invalid
oneOf(validator).validateMessage(message, group);
will(throwException(new InvalidMessageException()));
// Invalidate the message
oneOf(db).startTransaction(false);
will(returnValue(txn1));
oneOf(db).getMessageState(txn1, messageId);
will(returnValue(UNKNOWN));
oneOf(db).setMessageState(txn1, messageId, INVALID);
oneOf(db).deleteMessage(txn1, messageId);
oneOf(db).deleteMessageMetadata(txn1, messageId);
// The message has two dependents: 1 and 2
oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(twoDependents));
oneOf(db).endTransaction(txn1);
// Invalidate message 1
oneOf(db).startTransaction(false);
will(returnValue(txn2));
oneOf(db).getMessageState(txn2, messageId1);
will(returnValue(PENDING));
oneOf(db).setMessageState(txn2, messageId1, INVALID);
oneOf(db).deleteMessage(txn2, messageId1);
oneOf(db).deleteMessageMetadata(txn2, messageId1);
// Message 1 has one dependent: 3
oneOf(db).getMessageDependents(txn2, messageId1);
will(returnValue(Collections.singletonMap(messageId3, PENDING)));
oneOf(db).endTransaction(txn2);
// Invalidate message 2
oneOf(db).startTransaction(false);
will(returnValue(txn3));
oneOf(db).getMessageState(txn3, messageId2);
will(returnValue(PENDING));
oneOf(db).setMessageState(txn3, messageId2, INVALID);
oneOf(db).deleteMessage(txn3, messageId2);
oneOf(db).deleteMessageMetadata(txn3, messageId2);
// Message 2 has one dependent: 3 (same dependent as 1)
oneOf(db).getMessageDependents(txn3, messageId2);
will(returnValue(Collections.singletonMap(messageId3, PENDING)));
oneOf(db).endTransaction(txn3);
// Invalidate message 3 (via 1)
oneOf(db).startTransaction(false);
will(returnValue(txn4));
oneOf(db).getMessageState(txn4, messageId3);
will(returnValue(PENDING));
oneOf(db).setMessageState(txn4, messageId3, INVALID);
oneOf(db).deleteMessage(txn4, messageId3);
oneOf(db).deleteMessageMetadata(txn4, messageId3);
// Message 3 has one dependent: 4
oneOf(db).getMessageDependents(txn4, messageId3);
will(returnValue(Collections.singletonMap(messageId4, PENDING)));
oneOf(db).endTransaction(txn4);
// Invalidate message 3 (again, via 2)
oneOf(db).startTransaction(false);
will(returnValue(txn5));
oneOf(db).getMessageState(txn5, messageId3);
will(returnValue(INVALID)); // Already invalidated
oneOf(db).endTransaction(txn5);
// Invalidate message 4 (via 1 and 3)
oneOf(db).startTransaction(false);
will(returnValue(txn6));
oneOf(db).getMessageState(txn6, messageId4);
will(returnValue(PENDING));
oneOf(db).setMessageState(txn6, messageId4, INVALID);
oneOf(db).deleteMessage(txn6, messageId4);
oneOf(db).deleteMessageMetadata(txn6, messageId4);
// Message 4 has no dependents
oneOf(db).getMessageDependents(txn6, messageId4);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn6);
}}); }});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
@@ -716,8 +746,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
assertTrue(txn.isComplete()); assertTrue(txn.isComplete());
assertTrue(txn1.isComplete()); assertTrue(txn1.isComplete());
assertTrue(txn2.isComplete()); assertTrue(txn2.isComplete());
assertTrue(txn3.isComplete());
assertTrue(txn4.isComplete());
} }
@Test @Test
@@ -729,11 +757,25 @@ public class ValidationManagerImplTest extends BriarTestCase {
final MessageValidator validator = context.mock(MessageValidator.class); final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook = final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
final MessageId messageId4 = new MessageId(TestUtils.getRandomId());
final Message message3 = new Message(messageId3, groupId, timestamp,
raw);
final Message message4 = new Message(messageId4, groupId, timestamp,
raw);
final Map<MessageId, State> twoDependents = new LinkedHashMap<>();
twoDependents.put(messageId1, PENDING);
twoDependents.put(messageId2, PENDING);
final Map<MessageId, State> twoDependencies = new LinkedHashMap<>();
twoDependencies.put(messageId1, DELIVERED);
twoDependencies.put(messageId2, DELIVERED);
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false); final Transaction txn2 = new Transaction(null, false);
final Transaction txn3 = new Transaction(null, true); final Transaction txn3 = new Transaction(null, false);
final Transaction txn4 = new Transaction(null, false); final Transaction txn4 = new Transaction(null, false);
final Transaction txn5 = new Transaction(null, false);
final Transaction txn6 = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Load the group // Load the group
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -747,43 +789,114 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result // Store the validation result
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(Collections.emptyMap()));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, VALID); // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata);
oneOf(db).getRawMessage(txn1, messageId);
will(returnValue(raw));
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// The message has two pending dependents: 1 and 2
oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(twoDependents));
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Deliver first message // Check whether message 1 is ready to be delivered
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(hook).incomingMessage(txn2, message, metadata); oneOf(db).getMessageState(txn2, messageId1);
oneOf(db).getRawMessage(txn2, messageId); will(returnValue(PENDING));
will(returnValue(raw));
oneOf(db).setMessageState(txn2, message, clientId, DELIVERED);
oneOf(db).getMessageDependents(txn2, messageId);
will(returnValue(Collections.singletonMap(messageId1, PENDING)));
oneOf(db).getMessageDependencies(txn2, messageId1); oneOf(db).getMessageDependencies(txn2, messageId1);
will(returnValue(Collections.singletonMap(messageId2, DELIVERED))); will(returnValue(Collections.singletonMap(messageId, DELIVERED)));
oneOf(db).endTransaction(txn2); // Get message 1 and its metadata
// Also get the pending message for delivery oneOf(db).getRawMessage(txn2, messageId1);
oneOf(db).startTransaction(true); will(returnValue(raw));
will(returnValue(txn3)); oneOf(db).getGroup(txn2, groupId);
oneOf(db).getRawMessage(txn3, messageId1);
will(returnValue(message1.getRaw()));
oneOf(db).getGroup(txn3, message1.getGroupId());
will(returnValue(group)); will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn3, messageId1); oneOf(db).getMessageMetadataForValidator(txn2, messageId1);
will(returnValue(metadata)); will(returnValue(metadata));
// Deliver message 1
oneOf(hook).incomingMessage(txn2, message1, metadata);
oneOf(db).getRawMessage(txn2, messageId1);
will(returnValue(raw));
oneOf(db).setMessageState(txn2, messageId1, DELIVERED);
// Message 1 has one pending dependent: 3
oneOf(db).getMessageDependents(txn2, messageId1);
will(returnValue(Collections.singletonMap(messageId3, PENDING)));
oneOf(db).endTransaction(txn2);
// Check whether message 2 is ready to be delivered
oneOf(db).startTransaction(false);
will(returnValue(txn3));
oneOf(db).getMessageState(txn3, messageId2);
will(returnValue(PENDING));
oneOf(db).getMessageDependencies(txn3, messageId2);
will(returnValue(Collections.singletonMap(messageId, DELIVERED)));
// Get message 2 and its metadata
oneOf(db).getRawMessage(txn3, messageId2);
will(returnValue(raw));
oneOf(db).getGroup(txn3, groupId);
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn3, messageId2);
will(returnValue(metadata));
// Deliver message 2
oneOf(hook).incomingMessage(txn3, message2, metadata);
oneOf(db).getRawMessage(txn3, messageId2);
will(returnValue(raw));
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
// Message 2 has one pending dependent: 3 (same dependent as 1)
oneOf(db).getMessageDependents(txn3, messageId2);
will(returnValue(Collections.singletonMap(messageId3, PENDING)));
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
// Deliver the pending message // Check whether message 3 is ready to be delivered (via 1)
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(hook).incomingMessage(txn4, message1, metadata); oneOf(db).getMessageState(txn4, messageId3);
oneOf(db).getRawMessage(txn4, messageId1); will(returnValue(PENDING));
oneOf(db).getMessageDependencies(txn4, messageId3);
will(returnValue(twoDependencies));
// Get message 3 and its metadata
oneOf(db).getRawMessage(txn4, messageId3);
will(returnValue(raw)); will(returnValue(raw));
oneOf(db).setMessageState(txn4, message1, clientId, DELIVERED); oneOf(db).getGroup(txn4, groupId);
oneOf(db).getMessageDependents(txn4, messageId1); will(returnValue(group));
will(returnValue(Collections.emptyMap())); oneOf(db).getMessageMetadataForValidator(txn4, messageId3);
will(returnValue(metadata));
// Deliver message 3
oneOf(hook).incomingMessage(txn4, message3, metadata);
oneOf(db).getRawMessage(txn4, messageId3);
will(returnValue(raw));
oneOf(db).setMessageState(txn4, messageId3, DELIVERED);
// Message 3 has one pending dependent: 4
oneOf(db).getMessageDependents(txn4, messageId3);
will(returnValue(Collections.singletonMap(messageId4, PENDING)));
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
// Check whether message 3 is ready to be delivered (again, via 2)
oneOf(db).startTransaction(false);
will(returnValue(txn5));
oneOf(db).getMessageState(txn5, messageId3);
will(returnValue(DELIVERED)); // Already delivered
oneOf(db).endTransaction(txn5);
// Check whether message 4 is ready to be delivered (via 1 and 3)
oneOf(db).startTransaction(false);
will(returnValue(txn6));
oneOf(db).getMessageState(txn6, messageId4);
will(returnValue(PENDING));
oneOf(db).getMessageDependencies(txn6, messageId4);
will(returnValue(Collections.singletonMap(messageId3, DELIVERED)));
// Get message 4 and its metadata
oneOf(db).getRawMessage(txn6, messageId4);
will(returnValue(raw));
oneOf(db).getGroup(txn6, groupId);
will(returnValue(group));
oneOf(db).getMessageMetadataForValidator(txn6, messageId4);
will(returnValue(metadata));
// Deliver message 4
oneOf(hook).incomingMessage(txn6, message4, metadata);
oneOf(db).getRawMessage(txn6, messageId4);
will(returnValue(raw));
oneOf(db).setMessageState(txn6, messageId4, DELIVERED);
// Message 4 has no pending dependents
oneOf(db).getMessageDependents(txn6, messageId4);
will(returnValue(Collections.emptyMap()));
oneOf(db).endTransaction(txn6);
}}); }});
ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor, ValidationManagerImpl vm = new ValidationManagerImpl(db, dbExecutor,
@@ -799,6 +912,8 @@ public class ValidationManagerImplTest extends BriarTestCase {
assertTrue(txn2.isComplete()); assertTrue(txn2.isComplete());
assertTrue(txn3.isComplete()); assertTrue(txn3.isComplete());
assertTrue(txn4.isComplete()); assertTrue(txn4.isComplete());
assertTrue(txn5.isComplete());
assertTrue(txn6.isComplete());
} }
@Test @Test
@@ -810,6 +925,9 @@ public class ValidationManagerImplTest extends BriarTestCase {
final MessageValidator validator = context.mock(MessageValidator.class); final MessageValidator validator = context.mock(MessageValidator.class);
final IncomingMessageHook hook = final IncomingMessageHook hook =
context.mock(IncomingMessageHook.class); context.mock(IncomingMessageHook.class);
final Map<MessageId, State> twoDependencies = new LinkedHashMap<>();
twoDependencies.put(messageId, DELIVERED);
twoDependencies.put(messageId2, UNKNOWN);
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final Transaction txn1 = new Transaction(null, false); final Transaction txn1 = new Transaction(null, false);
final Transaction txn2 = new Transaction(null, false); final Transaction txn2 = new Transaction(null, false);
@@ -826,22 +944,23 @@ public class ValidationManagerImplTest extends BriarTestCase {
// Store the validation result // Store the validation result
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(Collections.emptyMap()));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, VALID); // Deliver the message
oneOf(hook).incomingMessage(txn1, message, metadata);
oneOf(db).getRawMessage(txn1, messageId);
will(returnValue(raw));
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
// Get any pending dependents
oneOf(db).getMessageDependents(txn1, messageId);
will(returnValue(Collections.singletonMap(messageId1, PENDING)));
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Deliver first message // Check whether the pending dependent is ready to be delivered
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(hook).incomingMessage(txn2, message, metadata); oneOf(db).getMessageState(txn2, messageId1);
oneOf(db).getRawMessage(txn2, messageId); will(returnValue(PENDING));
will(returnValue(raw));
oneOf(db).setMessageState(txn2, message, clientId, DELIVERED);
oneOf(db).getMessageDependents(txn2, messageId);
will(returnValue(Collections.singletonMap(messageId1, PENDING)));
oneOf(db).getMessageDependencies(txn2, messageId1); oneOf(db).getMessageDependencies(txn2, messageId1);
will(returnValue(Collections.singletonMap(messageId2, VALID))); will(returnValue(twoDependencies));
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
}}); }});
@@ -860,7 +979,6 @@ public class ValidationManagerImplTest extends BriarTestCase {
@Test @Test
public void testMessageDependencyCycle() throws Exception { public void testMessageDependencyCycle() throws Exception {
states.put(messageId1, UNKNOWN);
final MessageContext cycleContext = new MessageContext(metadata, final MessageContext cycleContext = new MessageContext(metadata,
Collections.singletonList(messageId)); Collections.singletonList(messageId));
@@ -891,9 +1009,9 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).addMessageDependencies(txn1, message, oneOf(db).addMessageDependencies(txn1, message,
validResultWithDependencies.getDependencies()); validResultWithDependencies.getDependencies());
oneOf(db).getMessageDependencies(txn1, messageId); oneOf(db).getMessageDependencies(txn1, messageId);
will(returnValue(states)); will(returnValue(Collections.singletonMap(messageId1, UNKNOWN)));
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata); oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
oneOf(db).setMessageState(txn1, message, clientId, PENDING); oneOf(db).setMessageState(txn1, messageId, PENDING);
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// Second message is coming in // Second message is coming in
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -912,7 +1030,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).getMessageDependencies(txn3, messageId1); oneOf(db).getMessageDependencies(txn3, messageId1);
will(returnValue(Collections.singletonMap(messageId, PENDING))); will(returnValue(Collections.singletonMap(messageId, PENDING)));
oneOf(db).mergeMessageMetadata(txn3, messageId1, metadata); oneOf(db).mergeMessageMetadata(txn3, messageId1, metadata);
oneOf(db).setMessageState(txn3, message1, clientId, PENDING); oneOf(db).setMessageState(txn3, messageId1, PENDING);
oneOf(db).endTransaction(txn3); oneOf(db).endTransaction(txn3);
}}); }});