Merge branch '382-message-dependencies' into 'master'

Introduce a MessageContext class to be used by all validators

This change will allow to pass message dependencies from the client validators to the `ValidationManager`.

Please see my thoughts in #382 for more details.

See merge request !197
This commit is contained in:
str4d
2016-05-20 02:49:04 +00:00
18 changed files with 209 additions and 72 deletions

View File

@@ -0,0 +1,28 @@
package org.briarproject.api.clients;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.BaseMessageContext;
import org.briarproject.api.sync.MessageId;
import java.util.Collection;
public class BdfMessageContext extends BaseMessageContext {
private final BdfDictionary dictionary;
public BdfMessageContext(BdfDictionary dictionary,
Collection<MessageId> dependencies) {
super(dependencies);
this.dictionary = dictionary;
}
public BdfMessageContext(BdfDictionary dictionary) {
this(dictionary, null);
}
public BdfDictionary getDictionary() {
return dictionary;
}
}

View File

@@ -5,6 +5,8 @@ import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.MessageContext;
public interface MessageQueueManager {
@@ -34,10 +36,11 @@ public interface MessageQueueManager {
interface QueueMessageValidator {
/**
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
* Validates the given message and returns its metadata and
* dependencies.
*/
Metadata validateMessage(QueueMessage q, Group g);
MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException;
}
interface IncomingQueueMessageHook {

View File

@@ -0,0 +1,17 @@
package org.briarproject.api.sync;
import java.util.Collection;
public abstract class BaseMessageContext {
private final Collection<MessageId> dependencies;
public BaseMessageContext(Collection<MessageId> dependencies) {
this.dependencies = dependencies;
}
public Collection<MessageId> getDependencies() {
return dependencies;
}
}

View File

@@ -0,0 +1,20 @@
package org.briarproject.api.sync;
import java.io.IOException;
/** An exception that indicates an invalid message. */
public class InvalidMessageException extends IOException {
public InvalidMessageException() {
super();
}
public InvalidMessageException(String str) {
super(str);
}
public InvalidMessageException(Throwable t) {
super(t);
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.api.sync;
import org.briarproject.api.db.Metadata;
import java.util.Collection;
public class MessageContext extends BaseMessageContext {
private final Metadata metadata;
public MessageContext(Metadata metadata,
Collection<MessageId> dependencies) {
super(dependencies);
this.metadata = metadata;
}
public MessageContext(Metadata metadata) {
this(metadata, null);
}
public Metadata getMetadata() {
return metadata;
}
}

View File

@@ -44,10 +44,11 @@ public interface ValidationManager {
interface MessageValidator {
/**
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
* Validates the given message and returns its metadata and
* dependencies.
*/
Metadata validateMessage(Message m, Group g);
MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException;
}
interface IncomingMessageHook {

View File

@@ -4,13 +4,16 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager.QueueMessageValidator;
import org.briarproject.api.clients.QueueMessage;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.api.system.Clock;
import org.briarproject.util.StringUtils;
@@ -37,43 +40,41 @@ public abstract class BdfMessageValidator implements MessageValidator,
this.clock = clock;
}
protected abstract BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException;
protected abstract BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException;
@Override
public Metadata validateMessage(Message m, Group g) {
public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
return validateMessage(m, g, MESSAGE_HEADER_LENGTH);
}
@Override
public Metadata validateMessage(QueueMessage q, Group g) {
public MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException {
return validateMessage(q, g, QUEUE_MESSAGE_HEADER_LENGTH);
}
private Metadata validateMessage(Message m, Group g, int headerLength) {
private MessageContext validateMessage(Message m, Group g, int headerLength)
throws InvalidMessageException {
// Reject the message if it's too far in the future
long now = clock.currentTimeMillis();
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
LOG.info("Timestamp is too far in the future");
return null;
throw new InvalidMessageException(
"Timestamp is too far in the future");
}
byte[] raw = m.getRaw();
if (raw.length <= headerLength) {
LOG.info("Message is too short");
return null;
throw new InvalidMessageException("Message is too short");
}
try {
BdfList body = clientHelper.toList(raw, headerLength,
raw.length - headerLength);
BdfDictionary meta = validateMessage(m, g, body);
if (meta == null) {
LOG.info("Invalid message");
return null;
}
return metadataEncoder.encode(meta);
BdfMessageContext result = validateMessage(m, g, body);
Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies());
} catch (FormatException e) {
LOG.info("Invalid message");
return null;
throw new InvalidMessageException(e);
}
}

View File

@@ -14,10 +14,12 @@ import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import java.util.ArrayList;
@@ -169,7 +171,8 @@ class MessageQueueManagerImpl implements MessageQueueManager {
}
@Override
public Metadata validateMessage(Message m, Group g) {
public MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException {
byte[] raw = m.getRaw();
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null;
long queuePosition = ByteUtils.readUint64(raw,

View File

@@ -3,6 +3,7 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
@@ -13,6 +14,7 @@ import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
@@ -39,8 +41,8 @@ class ForumPostValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException {
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// Parent ID, author, content type, forum post body, signature
checkSize(body, 5);
// Parent ID is optional
@@ -69,12 +71,10 @@ class ForumPostValidator extends BdfMessageValidator {
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// If there's an author there must be a signature and vice versa
if (author != null && sig == null) {
LOG.info("Author without signature");
return null;
throw new InvalidMessageException("Author without signature");
}
if (author == null && sig != null) {
LOG.info("Signature without author");
return null;
throw new InvalidMessageException("Signature without author");
}
// Verify the signature, if any
if (author != null) {
@@ -90,12 +90,10 @@ class ForumPostValidator extends BdfMessageValidator {
signature.initVerify(key);
signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) {
LOG.info("Invalid signature");
return null;
throw new InvalidMessageException("Invalid signature");
}
} catch (GeneralSecurityException e) {
LOG.info("Invalid public key");
return null;
throw new InvalidMessageException("Invalid public key");
}
}
// Return the metadata
@@ -111,6 +109,6 @@ class ForumPostValidator extends BdfMessageValidator {
}
meta.put("contentType", contentType);
meta.put("read", false);
return meta;
return new BdfMessageContext(meta);
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -36,7 +37,7 @@ class ForumSharingValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
BdfDictionary d = new BdfDictionary();
@@ -75,6 +76,6 @@ class ForumSharingValidator extends BdfMessageValidator {
d.put(SESSION_ID, id);
d.put(LOCAL, false);
d.put(TIME, m.getTimestamp());
return d;
return new BdfMessageContext(d);
}
}

View File

@@ -2,10 +2,11 @@ package org.briarproject.introduction;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock;
@@ -42,7 +43,7 @@ class IntroductionValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g, BdfList body)
protected BdfMessageContext validateMessage(Message m, Group g, BdfList body)
throws FormatException {
BdfDictionary d;
@@ -67,7 +68,7 @@ class IntroductionValidator extends BdfMessageValidator {
d.put(GROUP_ID, m.getGroupId());
d.put(MESSAGE_ID, m.getId());
d.put(MESSAGE_TIME, m.getTimestamp());
return d;
return new BdfMessageContext(d);
}
private BdfDictionary validateRequest(BdfList message)

View File

@@ -3,6 +3,7 @@ package org.briarproject.messaging;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ class PrivateMessageValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Parent ID, content type, private message body
checkSize(body, 3);
@@ -42,6 +43,6 @@ class PrivateMessageValidator extends BdfMessageValidator {
meta.put("contentType", contentType);
meta.put("local", false);
meta.put("read", false);
return meta;
return new BdfMessageContext(meta);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.properties;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -22,7 +23,7 @@ public class TransportPropertyValidator extends BdfMessageValidator {
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Transport ID, version, properties
checkSize(body, 3);
@@ -45,6 +46,6 @@ public class TransportPropertyValidator extends BdfMessageValidator {
meta.put("transportId", transportId);
meta.put("version", version);
meta.put("local", false);
return meta;
return new BdfMessageContext(meta);
}
}

View File

@@ -16,9 +16,11 @@ import org.briarproject.api.lifecycle.Service;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import java.util.LinkedList;
@@ -31,6 +33,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -152,31 +155,54 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (v == null) {
LOG.warning("No validator");
} else {
Metadata meta = v.validateMessage(m, g);
storeValidationResult(m, g.getClientId(), meta);
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 storeValidationResult(final Message m, final ClientId c,
final Metadata meta) {
private void storeMessageContext(final Message m, final ClientId c,
final MessageContext result) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Transaction txn = db.startTransaction(false);
try {
if (meta == null) {
db.setMessageValid(txn, m, c, false);
} else {
db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(txn, m, c, true);
db.setMessageShared(txn, m, true);
IncomingMessageHook hook = hooks.get(c);
if (hook != null)
hook.incomingMessage(txn, m, meta);
}
Metadata meta = result.getMetadata();
db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(txn, m, c, true);
db.setMessageShared(txn, m, true);
IncomingMessageHook hook = hooks.get(c);
if (hook != null)
hook.incomingMessage(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void markMessageInvalid(final Message m, final ClientId c) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Transaction txn = db.startTransaction(false);
try {
db.setMessageValid(txn, m, c, false);
txn.setComplete();
} finally {
db.endTransaction(txn);

View File

@@ -20,6 +20,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import org.hamcrest.Description;
import org.jmock.Expectations;
@@ -210,7 +211,9 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
new AtomicReference<MessageValidator>();
final QueueMessageValidator queueMessageValidator =
context.mock(QueueMessageValidator.class);
final Metadata messageMetadata = new Metadata();
final Metadata metadata = new Metadata();
final MessageContext messageContext =
new MessageContext(metadata);
// The message is valid, with a queue position of zero
final MessageId messageId = new MessageId(TestUtils.getRandomId());
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
@@ -224,7 +227,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
// The message should be delegated
oneOf(queueMessageValidator).validateMessage(
with(any(QueueMessage.class)), with(group));
will(returnValue(messageMetadata));
will(returnValue(messageContext));
}});
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
@@ -235,7 +238,8 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
MessageValidator delegate = captured.get();
assertNotNull(delegate);
// The message should be valid and the metadata should be returned
assertSame(messageMetadata, delegate.validateMessage(message, group));
assertSame(messageContext, delegate.validateMessage(message, group));
assertSame(metadata, messageContext.getMetadata());
context.assertIsSatisfied();
}

View File

@@ -85,7 +85,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
name, publicKey, text);
final BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body)
.getDictionary();
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -182,7 +183,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
final BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -199,7 +200,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT));
BdfDictionary result = validator.validateMessage(message, group, body);
BdfDictionary result = validator.validateMessage(message, group, body)
.getDictionary();
assertFalse(result.getBoolean(ACCEPT));
context.assertIsSatisfied();
@@ -288,7 +290,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -334,7 +336,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result =
validator.validateMessage(message, group, body);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));

View File

@@ -59,7 +59,8 @@ public class TransportPropertyValidatorTest extends BriarTestCase {
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
BdfDictionary result = tpv.validateMessage(message, group, body);
BdfDictionary result = tpv.validateMessage(message, group, body)
.getDictionary();
assertEquals("test", result.getString("transportId"));
assertEquals(4, result.getLong("version").longValue());

View File

@@ -14,10 +14,12 @@ import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.api.sync.ValidationManager.MessageValidator;
import org.briarproject.api.sync.MessageContext;
import org.briarproject.util.ByteUtils;
import org.jmock.Expectations;
import org.jmock.Mockery;
@@ -41,6 +43,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
private final Message message1 = new Message(messageId1, groupId, timestamp,
raw);
private final Metadata metadata = new Metadata();
final MessageContext validResult = new MessageContext(metadata);
private final ContactId contactId = new ContactId(234);
public ValidationManagerImplTest() {
@@ -80,7 +83,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn1);
// Validate the first message: valid
oneOf(validator).validateMessage(message, group);
will(returnValue(metadata));
will(returnValue(validResult));
// Store the validation result for the first message
oneOf(db).startTransaction(false);
will(returnValue(txn2));
@@ -100,7 +103,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn3);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn4));
@@ -154,7 +157,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn3));
@@ -211,7 +214,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn2);
// Validate the second message: invalid
oneOf(validator).validateMessage(message1, group);
will(returnValue(null));
will(throwException(new InvalidMessageException()));
// Store the validation result for the second message
oneOf(db).startTransaction(false);
will(returnValue(txn3));
@@ -248,7 +251,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
oneOf(db).endTransaction(txn);
// Validate the message: valid
oneOf(validator).validateMessage(message, group);
will(returnValue(metadata));
will(returnValue(validResult));
// Store the validation result
oneOf(db).startTransaction(false);
will(returnValue(txn1));