mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 12:19:54 +01:00
Merge branch '266-message-queues' into 'master'
Message queues. #266 A message queue is a group shared by two devices that delivers messages from each device to the other in order. The first 64 bits of the message body contain a sequence number that's incremented for each message sent in a given direction. The incoming and outgoing sequence numbers and information about any messages received out of order and waiting to be delivered are stored in the group metadata. See merge request !121
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package org.briarproject.clients;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager.IncomingQueueMessageHook;
|
||||
import org.briarproject.api.clients.QueueMessage;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import static org.briarproject.api.clients.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
public abstract class BdfIncomingMessageHook implements IncomingMessageHook,
|
||||
IncomingQueueMessageHook {
|
||||
|
||||
protected final ClientHelper clientHelper;
|
||||
protected final MetadataParser metadataParser;
|
||||
|
||||
protected BdfIncomingMessageHook(ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, Clock clock) {
|
||||
this.clientHelper = clientHelper;
|
||||
this.metadataParser = metadataParser;
|
||||
}
|
||||
|
||||
protected abstract void incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta) throws DbException,
|
||||
FormatException;
|
||||
|
||||
@Override
|
||||
public void incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException {
|
||||
incomingMessage(txn, m, meta, MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingMessage(Transaction txn, QueueMessage q, Metadata meta)
|
||||
throws DbException {
|
||||
incomingMessage(txn, q, meta, QUEUE_MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
private void incomingMessage(Transaction txn, Message m, Metadata meta,
|
||||
int headerLength) throws DbException {
|
||||
try {
|
||||
byte[] raw = m.getRaw();
|
||||
BdfList body = clientHelper.toList(raw, headerLength,
|
||||
raw.length - headerLength);
|
||||
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
||||
incomingMessage(txn, m, body, metaDictionary);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,26 @@ package org.briarproject.clients;
|
||||
|
||||
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.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.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.briarproject.api.clients.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
public abstract class BdfMessageValidator implements MessageValidator {
|
||||
public abstract class BdfMessageValidator implements MessageValidator,
|
||||
QueueMessageValidator {
|
||||
|
||||
protected static final Logger LOG =
|
||||
Logger.getLogger(BdfMessageValidator.class.getName());
|
||||
@@ -33,11 +37,20 @@ public abstract class BdfMessageValidator implements MessageValidator {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected abstract BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException;
|
||||
protected abstract BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException;
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
return validateMessage(m, g, MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(QueueMessage q, Group g) {
|
||||
return validateMessage(q, g, QUEUE_MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
private Metadata validateMessage(Message m, Group g, int headerLength) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
@@ -45,10 +58,14 @@ public abstract class BdfMessageValidator implements MessageValidator {
|
||||
return null;
|
||||
}
|
||||
byte[] raw = m.getRaw();
|
||||
if (raw.length <= headerLength) {
|
||||
LOG.info("Message is too short");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BdfList message = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
|
||||
raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfDictionary meta = validateMessage(message, g, m.getTimestamp());
|
||||
BdfList body = clientHelper.toList(raw, headerLength,
|
||||
raw.length - headerLength);
|
||||
BdfDictionary meta = validateMessage(m, g, body);
|
||||
if (meta == null) {
|
||||
LOG.info("Invalid message");
|
||||
return null;
|
||||
@@ -87,7 +104,7 @@ public abstract class BdfMessageValidator implements MessageValidator {
|
||||
}
|
||||
|
||||
protected void checkSize(BdfList list, int minSize, int maxSize)
|
||||
throws FormatException {
|
||||
throws FormatException {
|
||||
if (list != null) {
|
||||
if (list.size() < minSize) throw new FormatException();
|
||||
if (list.size() > maxSize) throw new FormatException();
|
||||
|
||||
@@ -3,13 +3,17 @@ package org.briarproject.clients;
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||
import org.briarproject.api.clients.QueueMessageFactory;
|
||||
|
||||
public class ClientsModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ClientHelper.class).to(ClientHelperImpl.class);
|
||||
bind(MessageQueueManager.class).to(MessageQueueManagerImpl.class);
|
||||
bind(PrivateGroupFactory.class).to(PrivateGroupFactoryImpl.class);
|
||||
bind(QueueMessageFactory.class).to(QueueMessageFactoryImpl.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package org.briarproject.clients;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
import org.briarproject.api.clients.QueueMessage;
|
||||
import org.briarproject.api.clients.QueueMessageFactory;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
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.GroupId;
|
||||
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.util.ByteUtils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.api.clients.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class MessageQueueManagerImpl implements MessageQueueManager {
|
||||
|
||||
private static final String OUTGOING_POSITION_KEY = "nextOut";
|
||||
private static final String INCOMING_POSITION_KEY = "nextIn";
|
||||
private static final String PENDING_MESSAGES_KEY = "pending";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(MessageQueueManagerImpl.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final QueueMessageFactory queueMessageFactory;
|
||||
private final ValidationManager validationManager;
|
||||
|
||||
@Inject
|
||||
MessageQueueManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
QueueMessageFactory queueMessageFactory,
|
||||
ValidationManager validationManager) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.queueMessageFactory = queueMessageFactory;
|
||||
this.validationManager = validationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueueMessage sendMessage(Transaction txn, Group queue,
|
||||
long timestamp, byte[] body, Metadata meta) throws DbException {
|
||||
QueueState queueState = loadQueueState(txn, queue.getId());
|
||||
long queuePosition = queueState.outgoingPosition;
|
||||
queueState.outgoingPosition++;
|
||||
saveQueueState(txn, queue.getId(), queueState);
|
||||
QueueMessage q = queueMessageFactory.createMessage(queue.getId(),
|
||||
timestamp, queuePosition, body);
|
||||
db.addLocalMessage(txn, q, queue.getClientId(), meta, true);
|
||||
return q;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerMessageValidator(ClientId c, QueueMessageValidator v) {
|
||||
validationManager.registerMessageValidator(c,
|
||||
new DelegatingMessageValidator(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIncomingMessageHook(ClientId c,
|
||||
IncomingQueueMessageHook hook) {
|
||||
validationManager.registerIncomingMessageHook(c,
|
||||
new DelegatingIncomingMessageHook(hook));
|
||||
}
|
||||
|
||||
private QueueState loadQueueState(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
TreeMap<Long, MessageId> pending = new TreeMap<Long, MessageId>();
|
||||
Metadata groupMeta = db.getGroupMetadata(txn, g);
|
||||
byte[] raw = groupMeta.get(QUEUE_STATE_KEY);
|
||||
if (raw == null) return new QueueState(0, 0, pending);
|
||||
BdfDictionary d = clientHelper.toDictionary(raw, 0, raw.length);
|
||||
long outgoingPosition = d.getLong(OUTGOING_POSITION_KEY);
|
||||
long incomingPosition = d.getLong(INCOMING_POSITION_KEY);
|
||||
BdfList pendingList = d.getList(PENDING_MESSAGES_KEY);
|
||||
for (int i = 0; i < pendingList.size(); i++) {
|
||||
BdfList item = pendingList.getList(i);
|
||||
if (item.size() != 2) throw new FormatException();
|
||||
pending.put(item.getLong(0), new MessageId(item.getRaw(1)));
|
||||
}
|
||||
return new QueueState(outgoingPosition, incomingPosition, pending);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveQueueState(Transaction txn, GroupId g,
|
||||
QueueState queueState) throws DbException {
|
||||
try {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(OUTGOING_POSITION_KEY, queueState.outgoingPosition);
|
||||
d.put(INCOMING_POSITION_KEY, queueState.incomingPosition);
|
||||
BdfList pendingList = new BdfList();
|
||||
for (Entry<Long, MessageId> e : queueState.pending.entrySet())
|
||||
pendingList.add(BdfList.of(e.getKey(), e.getValue()));
|
||||
d.put(PENDING_MESSAGES_KEY, pendingList);
|
||||
Metadata groupMeta = new Metadata();
|
||||
groupMeta.put(QUEUE_STATE_KEY, clientHelper.toByteArray(d));
|
||||
db.mergeGroupMetadata(txn, g, groupMeta);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueueState {
|
||||
|
||||
private long outgoingPosition, incomingPosition;
|
||||
private final TreeMap<Long, MessageId> pending;
|
||||
|
||||
QueueState(long outgoingPosition, long incomingPosition,
|
||||
TreeMap<Long, MessageId> pending) {
|
||||
this.outgoingPosition = outgoingPosition;
|
||||
this.incomingPosition = incomingPosition;
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
MessageId popIncomingMessageId() {
|
||||
Iterator<Entry<Long, MessageId>> it = pending.entrySet().iterator();
|
||||
if (!it.hasNext()) return null;
|
||||
Entry<Long, MessageId> e = it.next();
|
||||
if (!e.getKey().equals(incomingPosition)) return null;
|
||||
it.remove();
|
||||
incomingPosition++;
|
||||
return e.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DelegatingMessageValidator
|
||||
implements ValidationManager.MessageValidator {
|
||||
|
||||
private final QueueMessageValidator delegate;
|
||||
|
||||
DelegatingMessageValidator(QueueMessageValidator delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
byte[] raw = m.getRaw();
|
||||
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null;
|
||||
long queuePosition = ByteUtils.readUint64(raw,
|
||||
MESSAGE_HEADER_LENGTH);
|
||||
if (queuePosition < 0) return null;
|
||||
QueueMessage q = new QueueMessage(m.getId(), m.getGroupId(),
|
||||
m.getTimestamp(), queuePosition, raw);
|
||||
return delegate.validateMessage(q, g);
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegatingIncomingMessageHook implements IncomingMessageHook {
|
||||
|
||||
private final IncomingQueueMessageHook delegate;
|
||||
|
||||
DelegatingIncomingMessageHook(IncomingQueueMessageHook delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException {
|
||||
long queuePosition = ByteUtils.readUint64(m.getRaw(),
|
||||
MESSAGE_HEADER_LENGTH);
|
||||
QueueState queueState = loadQueueState(txn, m.getGroupId());
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Received message with position "
|
||||
+ queuePosition + ", expecting "
|
||||
+ queueState.incomingPosition);
|
||||
}
|
||||
if (queuePosition < queueState.incomingPosition) {
|
||||
// A message with this queue position has already been seen
|
||||
LOG.warning("Deleting message with duplicate position");
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
} else if (queuePosition > queueState.incomingPosition) {
|
||||
// The message is out of order, add it to the pending list
|
||||
LOG.info("Message is out of order, adding to pending list");
|
||||
queueState.pending.put(queuePosition, m.getId());
|
||||
saveQueueState(txn, m.getGroupId(), queueState);
|
||||
} else {
|
||||
// The message is in order, pass it to the delegate
|
||||
LOG.info("Message is in order, delivering");
|
||||
QueueMessage q = new QueueMessage(m.getId(), m.getGroupId(),
|
||||
m.getTimestamp(), queuePosition, m.getRaw());
|
||||
delegate.incomingMessage(txn, q, meta);
|
||||
queueState.incomingPosition++;
|
||||
// Pass any consecutive messages to the delegate
|
||||
MessageId id;
|
||||
while ((id = queueState.popIncomingMessageId()) != null) {
|
||||
byte[] raw = db.getRawMessage(txn, id);
|
||||
meta = db.getMessageMetadata(txn, id);
|
||||
q = queueMessageFactory.createMessage(id, raw);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Delivering pending message with position "
|
||||
+ q.getQueuePosition());
|
||||
}
|
||||
delegate.incomingMessage(txn, q, meta);
|
||||
}
|
||||
saveQueueState(txn, m.getGroupId(), queueState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,26 @@ package org.briarproject.clients;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.briarproject.api.Bytes;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
class PrivateGroupFactoryImpl implements PrivateGroupFactory {
|
||||
|
||||
private final GroupFactory groupFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ClientHelper clientHelper;
|
||||
|
||||
@Inject
|
||||
PrivateGroupFactoryImpl(GroupFactory groupFactory,
|
||||
BdfWriterFactory bdfWriterFactory) {
|
||||
ClientHelper clientHelper) {
|
||||
this.groupFactory = groupFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,22 +34,12 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
|
||||
}
|
||||
|
||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart();
|
||||
if (Bytes.COMPARATOR.compare(local, remote) < 0) {
|
||||
w.writeRaw(local.getBytes());
|
||||
w.writeRaw(remote.getBytes());
|
||||
} else {
|
||||
w.writeRaw(remote.getBytes());
|
||||
w.writeRaw(local.getBytes());
|
||||
}
|
||||
w.writeListEnd();
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
if (Bytes.COMPARATOR.compare(local, remote) < 0)
|
||||
return clientHelper.toByteArray(BdfList.of(local, remote));
|
||||
else return clientHelper.toByteArray(BdfList.of(remote, local));
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.briarproject.clients;
|
||||
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.clients.QueueMessage;
|
||||
import org.briarproject.api.clients.QueueMessageFactory;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.clients.QueueMessage.MAX_QUEUE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.clients.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class QueueMessageFactoryImpl implements QueueMessageFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
QueueMessageFactoryImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueueMessage createMessage(GroupId groupId, long timestamp,
|
||||
long queuePosition, byte[] body) {
|
||||
if (body.length > MAX_QUEUE_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH + body.length];
|
||||
System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(queuePosition, raw, MESSAGE_HEADER_LENGTH);
|
||||
System.arraycopy(body, 0, raw, QUEUE_MESSAGE_HEADER_LENGTH,
|
||||
body.length);
|
||||
MessageId id = new MessageId(crypto.hash(MessageId.LABEL, raw));
|
||||
return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueueMessage createMessage(MessageId id, byte[] raw) {
|
||||
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (raw.length > MAX_MESSAGE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] groupId = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
||||
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||
long queuePosition = ByteUtils.readUint64(raw, MESSAGE_HEADER_LENGTH);
|
||||
return new QueueMessage(id, new GroupId(groupId), timestamp,
|
||||
queuePosition, raw);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
@@ -20,15 +21,15 @@ class ForumListValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Version, forum list
|
||||
checkSize(message, 2);
|
||||
checkSize(body, 2);
|
||||
// Version
|
||||
long version = message.getLong(0);
|
||||
long version = body.getLong(0);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Forum list
|
||||
BdfList forumList = message.getList(1);
|
||||
BdfList forumList = body.getList(1);
|
||||
for (int i = 0; i < forumList.size(); i++) {
|
||||
BdfList forum = forumList.getList(i);
|
||||
// Name, salt
|
||||
|
||||
@@ -53,7 +53,8 @@ public class ForumModule extends AbstractModule {
|
||||
ForumSharingManagerImpl forumSharingManager) {
|
||||
contactManager.registerAddContactHook(forumSharingManager);
|
||||
contactManager.registerRemoveContactHook(forumSharingManager);
|
||||
validationManager.registerValidationHook(forumSharingManager);
|
||||
validationManager.registerIncomingMessageHook(
|
||||
ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
|
||||
return forumSharingManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,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.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
@@ -38,16 +39,16 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Parent ID, author, content type, forum post body, signature
|
||||
checkSize(message, 5);
|
||||
checkSize(body, 5);
|
||||
// Parent ID is optional
|
||||
byte[] parent = message.getOptionalRaw(0);
|
||||
byte[] parent = body.getOptionalRaw(0);
|
||||
checkLength(parent, UniqueId.LENGTH);
|
||||
// Author is optional
|
||||
Author author = null;
|
||||
BdfList authorList = message.getOptionalList(1);
|
||||
BdfList authorList = body.getOptionalList(1);
|
||||
if (authorList != null) {
|
||||
// Name, public key
|
||||
checkSize(authorList, 2);
|
||||
@@ -58,13 +59,13 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
author = authorFactory.createAuthor(name, publicKey);
|
||||
}
|
||||
// Content type
|
||||
String contentType = message.getString(2);
|
||||
String contentType = body.getString(2);
|
||||
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||
// Forum post body
|
||||
byte[] body = message.getRaw(3);
|
||||
checkLength(body, 0, MAX_FORUM_POST_BODY_LENGTH);
|
||||
byte[] forumPostBody = body.getRaw(3);
|
||||
checkLength(forumPostBody, 0, MAX_FORUM_POST_BODY_LENGTH);
|
||||
// Signature is optional
|
||||
byte[] sig = message.getOptionalRaw(4);
|
||||
byte[] sig = body.getOptionalRaw(4);
|
||||
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
|
||||
// If there's an author there must be a signature and vice versa
|
||||
if (author != null && sig == null) {
|
||||
@@ -82,7 +83,7 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
|
||||
// Serialise the data to be signed
|
||||
BdfList signed = BdfList.of(g.getId(), timestamp, parent,
|
||||
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent,
|
||||
authorList, contentType, body);
|
||||
// Verify the signature
|
||||
Signature signature = crypto.getSignature();
|
||||
@@ -99,7 +100,7 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
}
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("timestamp", timestamp);
|
||||
meta.put("timestamp", m.getTimestamp());
|
||||
if (parent != null) meta.put("parent", parent);
|
||||
if (author != null) {
|
||||
BdfDictionary authorMeta = new BdfDictionary();
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.briarproject.api.sync.GroupFactory;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager.ValidationHook;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
@@ -44,7 +44,7 @@ import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
||||
RemoveContactHook, ValidationHook {
|
||||
RemoveContactHook, IncomingMessageHook {
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"cd11a5d04dccd9e2931d6fc3df456313"
|
||||
@@ -103,15 +103,13 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validatingMessage(Transaction txn, Message m, ClientId c,
|
||||
Metadata meta) throws DbException {
|
||||
if (c.equals(CLIENT_ID)) {
|
||||
try {
|
||||
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||
setForumVisibility(txn, contactId, getVisibleForums(txn, m));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
public void incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException {
|
||||
try {
|
||||
ContactId contactId = getContactId(txn, m.getGroupId());
|
||||
setForumVisibility(txn, contactId, getVisibleForums(txn, m));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
@@ -21,22 +22,22 @@ class PrivateMessageValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Parent ID, content type, private message body
|
||||
checkSize(message, 3);
|
||||
checkSize(body, 3);
|
||||
// Parent ID is optional
|
||||
byte[] parentId = message.getOptionalRaw(0);
|
||||
byte[] parentId = body.getOptionalRaw(0);
|
||||
checkLength(parentId, UniqueId.LENGTH);
|
||||
// Content type
|
||||
String contentType = message.getString(1);
|
||||
String contentType = body.getString(1);
|
||||
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||
// Private message body
|
||||
byte[] body = message.getRaw(2);
|
||||
checkLength(body, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
byte[] privateMessageBody = body.getRaw(2);
|
||||
checkLength(privateMessageBody, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("timestamp", timestamp);
|
||||
meta.put("timestamp", m.getTimestamp());
|
||||
if (parentId != null) meta.put("parent", parentId);
|
||||
meta.put("contentType", contentType);
|
||||
meta.put("local", false);
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
@@ -22,21 +23,21 @@ class TransportPropertyValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Device ID, transport ID, version, properties
|
||||
checkSize(message, 4);
|
||||
checkSize(body, 4);
|
||||
// Device ID
|
||||
byte[] deviceId = message.getRaw(0);
|
||||
byte[] deviceId = body.getRaw(0);
|
||||
checkLength(deviceId, UniqueId.LENGTH);
|
||||
// Transport ID
|
||||
String transportId = message.getString(1);
|
||||
String transportId = body.getString(1);
|
||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
// Version
|
||||
long version = message.getLong(2);
|
||||
long version = body.getLong(2);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Properties
|
||||
BdfDictionary dictionary = message.getDictionary(3);
|
||||
BdfDictionary dictionary = body.getDictionary(3);
|
||||
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||
for (String key : dictionary.keySet()) {
|
||||
checkLength(key, 0, MAX_PROPERTY_LENGTH);
|
||||
|
||||
@@ -20,16 +20,13 @@ import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -46,7 +43,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
private final Executor dbExecutor;
|
||||
private final Executor cryptoExecutor;
|
||||
private final Map<ClientId, MessageValidator> validators;
|
||||
private final List<ValidationHook> hooks;
|
||||
private final Map<ClientId, IncomingMessageHook> hooks;
|
||||
|
||||
@Inject
|
||||
ValidationManagerImpl(DatabaseComponent db,
|
||||
@@ -56,7 +53,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
validators = new ConcurrentHashMap<ClientId, MessageValidator>();
|
||||
hooks = new CopyOnWriteArrayList<ValidationHook>();
|
||||
hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,8 +73,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerValidationHook(ValidationHook hook) {
|
||||
hooks.add(hook);
|
||||
public void registerIncomingMessageHook(ClientId c,
|
||||
IncomingMessageHook hook) {
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void getMessagesToValidate(final ClientId c) {
|
||||
@@ -170,8 +168,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
db.setMessageValid(txn, m, c, true);
|
||||
db.setMessageShared(txn, m, true);
|
||||
for (ValidationHook hook : hooks)
|
||||
hook.validatingMessage(txn, m, c, meta);
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook != null)
|
||||
hook.incomingMessage(txn, m, meta);
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user