mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-23 08:09:54 +01:00
Merge branch 'use-client-helper' into 'master'
Use client helper in existing clients Use the new ClientHelper to reduce boilerplate in existing clients. Add a BdfMessageValidator superclass for clients that format their messages as BDF lists and their metadata as BDF dictionaries (which all existing clients do). See merge request !115
This commit is contained in:
@@ -19,6 +19,7 @@ import android.widget.Toast;
|
|||||||
import org.briarproject.R;
|
import org.briarproject.R;
|
||||||
import org.briarproject.android.BriarActivity;
|
import org.briarproject.android.BriarActivity;
|
||||||
import org.briarproject.android.util.BriarRecyclerView;
|
import org.briarproject.android.util.BriarRecyclerView;
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.android.AndroidNotificationManager;
|
import org.briarproject.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
@@ -47,8 +48,6 @@ import org.briarproject.api.sync.Message;
|
|||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -430,9 +429,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
try {
|
try {
|
||||||
storeMessage(privateMessageFactory.createPrivateMessage(
|
storeMessage(privateMessageFactory.createPrivateMessage(
|
||||||
groupId, timestamp, null, "text/plain", body));
|
groupId, timestamp, null, "text/plain", body));
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.briarproject.android.identity.LocalAuthorItemComparator;
|
|||||||
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
|
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
|
||||||
import org.briarproject.android.util.CommonLayoutParams;
|
import org.briarproject.android.util.CommonLayoutParams;
|
||||||
import org.briarproject.android.util.LayoutUtils;
|
import org.briarproject.android.util.LayoutUtils;
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.CryptoExecutor;
|
import org.briarproject.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.api.crypto.KeyParser;
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
@@ -39,7 +40,6 @@ import org.briarproject.api.sync.GroupId;
|
|||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -281,7 +281,7 @@ implements OnItemSelectedListener, OnClickListener {
|
|||||||
}
|
}
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} catch (IOException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
storePost(p);
|
storePost(p);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
@@ -13,6 +14,13 @@ import java.util.Map;
|
|||||||
|
|
||||||
public interface ClientHelper {
|
public interface ClientHelper {
|
||||||
|
|
||||||
|
void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
|
||||||
|
boolean shared) throws DbException, FormatException;
|
||||||
|
|
||||||
|
void addLocalMessage(Transaction txn, Message m, ClientId c,
|
||||||
|
BdfDictionary metadata, boolean shared) throws DbException,
|
||||||
|
FormatException;
|
||||||
|
|
||||||
Message createMessage(GroupId g, long timestamp, BdfDictionary body)
|
Message createMessage(GroupId g, long timestamp, BdfDictionary body)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
@@ -59,4 +67,13 @@ public interface ClientHelper {
|
|||||||
|
|
||||||
void mergeMessageMetadata(Transaction txn, MessageId m,
|
void mergeMessageMetadata(Transaction txn, MessageId m,
|
||||||
BdfDictionary metadata) throws DbException, FormatException;
|
BdfDictionary metadata) throws DbException, FormatException;
|
||||||
|
|
||||||
|
byte[] toByteArray(BdfDictionary dictionary) throws FormatException;
|
||||||
|
|
||||||
|
byte[] toByteArray(BdfList list) throws FormatException;
|
||||||
|
|
||||||
|
BdfDictionary toDictionary(byte[] b, int off, int len)
|
||||||
|
throws FormatException;
|
||||||
|
|
||||||
|
BdfList toList(byte[] b, int off, int len) throws FormatException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
package org.briarproject.api.forum;
|
package org.briarproject.api.forum;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.crypto.PrivateKey;
|
import org.briarproject.api.crypto.PrivateKey;
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.Author;
|
||||||
import org.briarproject.api.sync.GroupId;
|
import org.briarproject.api.sync.GroupId;
|
||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
public interface ForumPostFactory {
|
public interface ForumPostFactory {
|
||||||
|
|
||||||
ForumPost createAnonymousPost(GroupId groupId, long timestamp,
|
ForumPost createAnonymousPost(GroupId groupId, long timestamp,
|
||||||
MessageId parent, String contentType, byte[] body)
|
MessageId parent, String contentType, byte[] body)
|
||||||
throws IOException, GeneralSecurityException;
|
throws FormatException;
|
||||||
|
|
||||||
ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
|
ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
|
||||||
MessageId parent, Author author, String contentType, byte[] body,
|
MessageId parent, Author author, String contentType, byte[] body,
|
||||||
PrivateKey privateKey) throws IOException,
|
PrivateKey privateKey) throws FormatException,
|
||||||
GeneralSecurityException;
|
GeneralSecurityException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package org.briarproject.api.messaging;
|
package org.briarproject.api.messaging;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.sync.GroupId;
|
import org.briarproject.api.sync.GroupId;
|
||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
public interface PrivateMessageFactory {
|
public interface PrivateMessageFactory {
|
||||||
|
|
||||||
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
MessageId parent, String contentType, byte[] body)
|
MessageId parent, String contentType, byte[] body)
|
||||||
throws IOException, GeneralSecurityException;
|
throws FormatException;
|
||||||
}
|
}
|
||||||
|
|||||||
114
briar-core/src/org/briarproject/clients/BdfMessageValidator.java
Normal file
114
briar-core/src/org/briarproject/clients/BdfMessageValidator.java
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package org.briarproject.clients;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
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.system.Clock;
|
||||||
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
protected static final Logger LOG =
|
||||||
|
Logger.getLogger(BdfMessageValidator.class.getName());
|
||||||
|
|
||||||
|
protected final ClientHelper clientHelper;
|
||||||
|
protected final MetadataEncoder metadataEncoder;
|
||||||
|
protected final Clock clock;
|
||||||
|
|
||||||
|
protected BdfMessageValidator(ClientHelper clientHelper,
|
||||||
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.metadataEncoder = metadataEncoder;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract BdfDictionary validateMessage(BdfList message, Group g,
|
||||||
|
long timestamp) throws FormatException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Metadata validateMessage(Message m, Group g) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
byte[] raw = m.getRaw();
|
||||||
|
try {
|
||||||
|
BdfList message = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
|
||||||
|
raw.length - MESSAGE_HEADER_LENGTH);
|
||||||
|
BdfDictionary meta = validateMessage(message, g, m.getTimestamp());
|
||||||
|
if (meta == null) {
|
||||||
|
LOG.info("Invalid message");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return metadataEncoder.encode(meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.info("Invalid message");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkLength(String s, int minLength, int maxLength)
|
||||||
|
throws FormatException {
|
||||||
|
if (s != null) {
|
||||||
|
int length = StringUtils.toUtf8(s).length;
|
||||||
|
if (length < minLength) throw new FormatException();
|
||||||
|
if (length > maxLength) throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkLength(String s, int length) throws FormatException {
|
||||||
|
if (s != null && StringUtils.toUtf8(s).length != length)
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkLength(byte[] b, int minLength, int maxLength)
|
||||||
|
throws FormatException {
|
||||||
|
if (b != null) {
|
||||||
|
if (b.length < minLength) throw new FormatException();
|
||||||
|
if (b.length > maxLength) throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkLength(byte[] b, int length) throws FormatException {
|
||||||
|
if (b != null && b.length != length) throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkSize(BdfList list, int minSize, int maxSize)
|
||||||
|
throws FormatException {
|
||||||
|
if (list != null) {
|
||||||
|
if (list.size() < minSize) throw new FormatException();
|
||||||
|
if (list.size() > maxSize) throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkSize(BdfList list, int size) throws FormatException {
|
||||||
|
if (list != null && list.size() != size) throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkSize(BdfDictionary dictionary, int minSize,
|
||||||
|
int maxSize) throws FormatException {
|
||||||
|
if (dictionary != null) {
|
||||||
|
if (dictionary.size() < minSize) throw new FormatException();
|
||||||
|
if (dictionary.size() > maxSize) throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkSize(BdfDictionary dictionary, int size)
|
||||||
|
throws FormatException {
|
||||||
|
if (dictionary != null && dictionary.size() != size)
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ 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;
|
||||||
@@ -53,38 +54,35 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
this.metadataEncoder = metadataEncoder;
|
this.metadataEncoder = metadataEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
|
||||||
|
boolean shared) throws DbException, FormatException {
|
||||||
|
Transaction txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
addLocalMessage(txn, m, c, metadata, shared);
|
||||||
|
txn.setComplete();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLocalMessage(Transaction txn, Message m, ClientId c,
|
||||||
|
BdfDictionary metadata, boolean shared)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
db.addLocalMessage(txn, m, c, metadataEncoder.encode(metadata), shared);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
|
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
return messageFactory.createMessage(g, timestamp, toByteArray(body));
|
||||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
|
||||||
try {
|
|
||||||
writer.writeDictionary(body);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
byte[] raw = out.toByteArray();
|
|
||||||
return messageFactory.createMessage(g, timestamp, raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message createMessage(GroupId g, long timestamp, BdfList body)
|
public Message createMessage(GroupId g, long timestamp, BdfList body)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
return messageFactory.createMessage(g, timestamp, toByteArray(body));
|
||||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
|
||||||
try {
|
|
||||||
writer.writeList(body);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
byte[] raw = out.toByteArray();
|
|
||||||
return messageFactory.createMessage(g, timestamp, raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -106,20 +104,8 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
byte[] raw = db.getRawMessage(txn, m);
|
byte[] raw = db.getRawMessage(txn, m);
|
||||||
if (raw == null) return null;
|
if (raw == null) return null;
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
return toDictionary(raw, MESSAGE_HEADER_LENGTH,
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
raw.length - MESSAGE_HEADER_LENGTH);
|
||||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
|
||||||
BdfDictionary dictionary;
|
|
||||||
try {
|
|
||||||
dictionary = reader.readDictionary();
|
|
||||||
if (!reader.eof()) throw new FormatException();
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return dictionary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -141,20 +127,8 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
byte[] raw = db.getRawMessage(txn, m);
|
byte[] raw = db.getRawMessage(txn, m);
|
||||||
if (raw == null) return null;
|
if (raw == null) return null;
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
return toList(raw, MESSAGE_HEADER_LENGTH,
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
raw.length - MESSAGE_HEADER_LENGTH);
|
||||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
|
||||||
BdfList list;
|
|
||||||
try {
|
|
||||||
list = reader.readList();
|
|
||||||
if (!reader.eof()) throw new FormatException();
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -259,4 +233,63 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
BdfDictionary metadata) throws DbException, FormatException {
|
BdfDictionary metadata) throws DbException, FormatException {
|
||||||
db.mergeMessageMetadata(txn, m, metadataEncoder.encode(metadata));
|
db.mergeMessageMetadata(txn, m, metadataEncoder.encode(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toByteArray(BdfDictionary dictionary) throws FormatException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||||
|
try {
|
||||||
|
writer.writeDictionary(dictionary);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toByteArray(BdfList list) throws FormatException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||||
|
try {
|
||||||
|
writer.writeList(list);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfDictionary toDictionary(byte[] b, int off, int len)
|
||||||
|
throws FormatException {
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||||
|
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||||
|
try {
|
||||||
|
BdfDictionary dictionary = reader.readDictionary();
|
||||||
|
if (!reader.eof()) throw new FormatException();
|
||||||
|
return dictionary;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfList toList(byte[] b, int off, int len) throws FormatException {
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||||
|
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||||
|
try {
|
||||||
|
BdfList list = reader.readList();
|
||||||
|
if (!reader.eof()) throw new FormatException();
|
||||||
|
return list;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,47 @@
|
|||||||
package org.briarproject.forum;
|
package org.briarproject.forum;
|
||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.db.Metadata;
|
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.Message;
|
import org.briarproject.api.system.Clock;
|
||||||
import org.briarproject.api.sync.MessageValidator;
|
import org.briarproject.clients.BdfMessageValidator;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
|
|
||||||
class ForumListValidator implements MessageValidator {
|
class ForumListValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
private static final Logger LOG =
|
ForumListValidator(ClientHelper clientHelper,
|
||||||
Logger.getLogger(ForumListValidator.class.getName());
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
|
super(clientHelper, metadataEncoder, clock);
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
|
|
||||||
ForumListValidator(BdfReaderFactory bdfReaderFactory,
|
|
||||||
MetadataEncoder metadataEncoder) {
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Metadata validateMessage(Message m, Group g) {
|
public BdfDictionary validateMessage(BdfList message, Group g,
|
||||||
try {
|
long timestamp) throws FormatException {
|
||||||
// Parse the message body
|
// Version, forum list
|
||||||
byte[] raw = m.getRaw();
|
checkSize(message, 2);
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
// Version
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
long version = message.getLong(0);
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
if (version < 0) throw new FormatException();
|
||||||
r.readListStart();
|
// Forum list
|
||||||
long version = r.readLong();
|
BdfList forumList = message.getList(1);
|
||||||
if (version < 0) throw new FormatException();
|
for (int i = 0; i < forumList.size(); i++) {
|
||||||
r.readListStart();
|
BdfList forum = forumList.getList(i);
|
||||||
while (!r.hasListEnd()) {
|
// Name, salt
|
||||||
r.readListStart();
|
checkSize(forum, 2);
|
||||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
String name = forum.getString(0);
|
||||||
if (name.length() == 0) throw new FormatException();
|
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
|
||||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
byte[] salt = forum.getRaw(1);
|
||||||
if (salt.length != FORUM_SALT_LENGTH)
|
checkLength(salt, FORUM_SALT_LENGTH);
|
||||||
throw new FormatException();
|
|
||||||
r.readListEnd();
|
|
||||||
}
|
|
||||||
r.readListEnd();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
// Return the metadata
|
|
||||||
BdfDictionary d = new BdfDictionary();
|
|
||||||
d.put("version", version);
|
|
||||||
d.put("local", false);
|
|
||||||
return metadataEncoder.encode(d);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.info("Invalid forum list");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
// Return the metadata
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put("version", version);
|
||||||
|
meta.put("local", false);
|
||||||
|
return meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,12 @@ package org.briarproject.forum;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.api.data.MetadataParser;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
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.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
import org.briarproject.api.forum.Forum;
|
import org.briarproject.api.forum.Forum;
|
||||||
import org.briarproject.api.forum.ForumManager;
|
import org.briarproject.api.forum.ForumManager;
|
||||||
@@ -26,8 +23,6 @@ import org.briarproject.api.sync.GroupId;
|
|||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -36,16 +31,10 @@ 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.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
|
||||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
|
||||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
|
||||||
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
|
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
|
||||||
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
|
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
|
||||||
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
|
|
||||||
class ForumManagerImpl implements ForumManager {
|
class ForumManagerImpl implements ForumManager {
|
||||||
|
|
||||||
@@ -53,21 +42,13 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
"859a7be50dca035b64bd6902fb797097"
|
"859a7be50dca035b64bd6902fb797097"
|
||||||
+ "795af837abbf8c16d750b3c2ccc186ea"));
|
+ "795af837abbf8c16d750b3c2ccc186ea"));
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(ForumManagerImpl.class.getName());
|
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
private final ClientHelper clientHelper;
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final MetadataParser metadataParser;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory,
|
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper) {
|
||||||
MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
this.clientHelper = clientHelper;
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.metadataParser = metadataParser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,29 +59,21 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
@Override
|
@Override
|
||||||
public void addLocalPost(ForumPost p) throws DbException {
|
public void addLocalPost(ForumPost p) throws DbException {
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("timestamp", p.getMessage().getTimestamp());
|
meta.put("timestamp", p.getMessage().getTimestamp());
|
||||||
if (p.getParent() != null)
|
if (p.getParent() != null) meta.put("parent", p.getParent());
|
||||||
d.put("parent", p.getParent().getBytes());
|
|
||||||
if (p.getAuthor() != null) {
|
if (p.getAuthor() != null) {
|
||||||
Author a = p.getAuthor();
|
Author a = p.getAuthor();
|
||||||
BdfDictionary d1 = new BdfDictionary();
|
BdfDictionary authorMeta = new BdfDictionary();
|
||||||
d1.put("id", a.getId().getBytes());
|
authorMeta.put("id", a.getId());
|
||||||
d1.put("name", a.getName());
|
authorMeta.put("name", a.getName());
|
||||||
d1.put("publicKey", a.getPublicKey());
|
authorMeta.put("publicKey", a.getPublicKey());
|
||||||
d.put("author", d1);
|
meta.put("author", authorMeta);
|
||||||
}
|
|
||||||
d.put("contentType", p.getContentType());
|
|
||||||
d.put("local", true);
|
|
||||||
d.put("read", true);
|
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
|
||||||
Transaction txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
db.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
}
|
||||||
|
meta.put("contentType", p.getContentType());
|
||||||
|
meta.put("local", true);
|
||||||
|
meta.put("read", true);
|
||||||
|
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -145,34 +118,11 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
@Override
|
@Override
|
||||||
public byte[] getPostBody(MessageId m) throws DbException {
|
public byte[] getPostBody(MessageId m) throws DbException {
|
||||||
try {
|
try {
|
||||||
byte[] raw;
|
// Parent ID, author, content type, forum post body, signature
|
||||||
Transaction txn = db.startTransaction();
|
BdfList message = clientHelper.getMessageAsList(m);
|
||||||
try {
|
return message.getRaw(3);
|
||||||
raw = db.getRawMessage(txn, m);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
|
||||||
r.readListStart();
|
|
||||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
|
||||||
else r.skipNull(); // No parent
|
|
||||||
if (r.hasList()) r.skipList(); // Author
|
|
||||||
else r.skipNull(); // No author
|
|
||||||
r.skipString(); // Content type
|
|
||||||
byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
|
||||||
if (r.hasRaw()) r.skipRaw(); // Signature
|
|
||||||
else r.skipNull();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return postBody;
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +131,7 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
|
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
|
||||||
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
|
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
|
||||||
Map<MessageId, Metadata> metadata;
|
Map<MessageId, BdfDictionary> metadata;
|
||||||
Transaction txn = db.startTransaction();
|
Transaction txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
// Load the IDs of the user's identities
|
// Load the IDs of the user's identities
|
||||||
@@ -191,20 +141,22 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
for (Contact c : db.getContacts(txn))
|
for (Contact c : db.getContacts(txn))
|
||||||
contactAuthorIds.add(c.getAuthor().getId());
|
contactAuthorIds.add(c.getAuthor().getId());
|
||||||
// Load the metadata
|
// Load the metadata
|
||||||
metadata = db.getMessageMetadata(txn, g);
|
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
txn.setComplete();
|
txn.setComplete();
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
|
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
|
||||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
BdfDictionary meta = entry.getValue();
|
||||||
long timestamp = d.getLong("timestamp");
|
long timestamp = meta.getLong("timestamp");
|
||||||
Author author = null;
|
Author author = null;
|
||||||
Author.Status authorStatus = ANONYMOUS;
|
Author.Status authorStatus = ANONYMOUS;
|
||||||
BdfDictionary d1 = d.getDictionary("author", null);
|
BdfDictionary d1 = meta.getDictionary("author", null);
|
||||||
if (d1 != null) {
|
if (d1 != null) {
|
||||||
AuthorId authorId = new AuthorId(d1.getRaw("id"));
|
AuthorId authorId = new AuthorId(d1.getRaw("id"));
|
||||||
String name = d1.getString("name");
|
String name = d1.getString("name");
|
||||||
@@ -216,13 +168,12 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
authorStatus = VERIFIED;
|
authorStatus = VERIFIED;
|
||||||
else authorStatus = UNKNOWN;
|
else authorStatus = UNKNOWN;
|
||||||
}
|
}
|
||||||
String contentType = d.getString("contentType");
|
String contentType = meta.getString("contentType");
|
||||||
boolean read = d.getBoolean("read");
|
boolean read = meta.getBoolean("read");
|
||||||
headers.add(new ForumPostHeader(e.getKey(), timestamp, author,
|
headers.add(new ForumPostHeader(entry.getKey(), timestamp,
|
||||||
authorStatus, contentType, read));
|
author, authorStatus, contentType, read));
|
||||||
} catch (FormatException ex) {
|
} catch (FormatException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
throw new DbException(e);
|
||||||
LOG.log(WARNING, ex.toString(), ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
@@ -231,36 +182,18 @@ class ForumManagerImpl implements ForumManager {
|
|||||||
@Override
|
@Override
|
||||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("read", read);
|
meta.put("read", read);
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
clientHelper.mergeMessageMetadata(m, meta);
|
||||||
Transaction txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
db.mergeMessageMetadata(txn, m, meta);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Forum parseForum(Group g) throws FormatException {
|
private Forum parseForum(Group g) throws FormatException {
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
|
byte[] descriptor = g.getDescriptor();
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
// Name, salt
|
||||||
try {
|
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
|
||||||
r.readListStart();
|
return new Forum(g, forum.getString(0), forum.getRaw(1));
|
||||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
|
||||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return new Forum(g, name, salt);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,14 @@ package org.briarproject.forum;
|
|||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.data.ObjectReader;
|
|
||||||
import org.briarproject.api.forum.ForumManager;
|
import org.briarproject.api.forum.ForumManager;
|
||||||
import org.briarproject.api.forum.ForumPostFactory;
|
import org.briarproject.api.forum.ForumPostFactory;
|
||||||
import org.briarproject.api.forum.ForumSharingManager;
|
import org.briarproject.api.forum.ForumSharingManager;
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
import org.briarproject.api.sync.ValidationManager;
|
import org.briarproject.api.sync.ValidationManager;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
|
||||||
@@ -29,13 +27,10 @@ public class ForumModule extends AbstractModule {
|
|||||||
@Provides @Singleton
|
@Provides @Singleton
|
||||||
ForumPostValidator getForumPostValidator(
|
ForumPostValidator getForumPostValidator(
|
||||||
ValidationManager validationManager, CryptoComponent crypto,
|
ValidationManager validationManager, CryptoComponent crypto,
|
||||||
BdfReaderFactory bdfReaderFactory,
|
AuthorFactory authorFactory, ClientHelper clientHelper,
|
||||||
BdfWriterFactory bdfWriterFactory,
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
|
|
||||||
Clock clock) {
|
|
||||||
ForumPostValidator validator = new ForumPostValidator(crypto,
|
ForumPostValidator validator = new ForumPostValidator(crypto,
|
||||||
bdfReaderFactory, bdfWriterFactory, authorReader,
|
authorFactory, clientHelper, metadataEncoder, clock);
|
||||||
metadataEncoder, clock);
|
|
||||||
validationManager.registerMessageValidator(
|
validationManager.registerMessageValidator(
|
||||||
ForumManagerImpl.CLIENT_ID, validator);
|
ForumManagerImpl.CLIENT_ID, validator);
|
||||||
return validator;
|
return validator;
|
||||||
@@ -43,11 +38,10 @@ public class ForumModule extends AbstractModule {
|
|||||||
|
|
||||||
@Provides @Singleton
|
@Provides @Singleton
|
||||||
ForumListValidator getForumListValidator(
|
ForumListValidator getForumListValidator(
|
||||||
ValidationManager validationManager,
|
ValidationManager validationManager, ClientHelper clientHelper,
|
||||||
BdfReaderFactory bdfReaderFactory,
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
MetadataEncoder metadataEncoder) {
|
ForumListValidator validator = new ForumListValidator(clientHelper,
|
||||||
ForumListValidator validator = new ForumListValidator(bdfReaderFactory,
|
metadataEncoder, clock);
|
||||||
metadataEncoder);
|
|
||||||
validationManager.registerMessageValidator(
|
validationManager.registerMessageValidator(
|
||||||
ForumSharingManagerImpl.CLIENT_ID, validator);
|
ForumSharingManagerImpl.CLIENT_ID, validator);
|
||||||
return validator;
|
return validator;
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package org.briarproject.forum;
|
package org.briarproject.forum;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.PrivateKey;
|
import org.briarproject.api.crypto.PrivateKey;
|
||||||
import org.briarproject.api.crypto.Signature;
|
import org.briarproject.api.crypto.Signature;
|
||||||
import org.briarproject.api.data.BdfWriter;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.api.forum.ForumPost;
|
import org.briarproject.api.forum.ForumPost;
|
||||||
import org.briarproject.api.forum.ForumPostFactory;
|
import org.briarproject.api.forum.ForumPostFactory;
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.Author;
|
||||||
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.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -26,46 +24,33 @@ import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENG
|
|||||||
class ForumPostFactoryImpl implements ForumPostFactory {
|
class ForumPostFactoryImpl implements ForumPostFactory {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final MessageFactory messageFactory;
|
private final ClientHelper clientHelper;
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ForumPostFactoryImpl(CryptoComponent crypto, MessageFactory messageFactory,
|
ForumPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
|
||||||
BdfWriterFactory bdfWriterFactory) {
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.messageFactory = messageFactory;
|
this.clientHelper = clientHelper;
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ForumPost createAnonymousPost(GroupId groupId, long timestamp,
|
public ForumPost createAnonymousPost(GroupId groupId, long timestamp,
|
||||||
MessageId parent, String contentType, byte[] body)
|
MessageId parent, String contentType, byte[] body)
|
||||||
throws IOException, GeneralSecurityException {
|
throws FormatException {
|
||||||
// Validate the arguments
|
// Validate the arguments
|
||||||
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
|
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
// Serialise the message to a buffer
|
// Serialise the message
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
BdfList message = BdfList.of(parent, null, contentType, body, null);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
||||||
w.writeListStart();
|
|
||||||
if (parent == null) w.writeNull();
|
|
||||||
else w.writeRaw(parent.getBytes());
|
|
||||||
w.writeNull(); // No author
|
|
||||||
w.writeString(contentType);
|
|
||||||
w.writeRaw(body);
|
|
||||||
w.writeNull(); // No signature
|
|
||||||
w.writeListEnd();
|
|
||||||
Message m = messageFactory.createMessage(groupId, timestamp,
|
|
||||||
out.toByteArray());
|
|
||||||
return new ForumPost(m, parent, null, contentType);
|
return new ForumPost(m, parent, null, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
|
public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
|
||||||
MessageId parent, Author author, String contentType, byte[] body,
|
MessageId parent, Author author, String contentType, byte[] body,
|
||||||
PrivateKey privateKey) throws IOException,
|
PrivateKey privateKey) throws FormatException,
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
// Validate the arguments
|
// Validate the arguments
|
||||||
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||||
@@ -73,42 +58,19 @@ class ForumPostFactoryImpl implements ForumPostFactory {
|
|||||||
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
|
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
// Serialise the data to be signed
|
// Serialise the data to be signed
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
BdfList authorList = BdfList.of(author.getName(),
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
author.getPublicKey());
|
||||||
w.writeListStart();
|
BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
|
||||||
w.writeRaw(groupId.getBytes());
|
contentType, body);
|
||||||
w.writeLong(timestamp);
|
|
||||||
if (parent == null) w.writeNull();
|
|
||||||
else w.writeRaw(parent.getBytes());
|
|
||||||
writeAuthor(w, author);
|
|
||||||
w.writeString(contentType);
|
|
||||||
w.writeRaw(body);
|
|
||||||
w.writeListEnd();
|
|
||||||
// Generate the signature
|
// Generate the signature
|
||||||
Signature signature = crypto.getSignature();
|
Signature signature = crypto.getSignature();
|
||||||
signature.initSign(privateKey);
|
signature.initSign(privateKey);
|
||||||
signature.update(out.toByteArray());
|
signature.update(clientHelper.toByteArray(signed));
|
||||||
byte[] sig = signature.sign();
|
byte[] sig = signature.sign();
|
||||||
// Serialise the signed message
|
// Serialise the signed message
|
||||||
out.reset();
|
BdfList message = BdfList.of(parent, authorList, contentType, body,
|
||||||
w = bdfWriterFactory.createWriter(out);
|
sig);
|
||||||
w.writeListStart();
|
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
||||||
if (parent == null) w.writeNull();
|
|
||||||
else w.writeRaw(parent.getBytes());
|
|
||||||
writeAuthor(w, author);
|
|
||||||
w.writeString(contentType);
|
|
||||||
w.writeRaw(body);
|
|
||||||
w.writeRaw(sig);
|
|
||||||
w.writeListEnd();
|
|
||||||
Message m = messageFactory.createMessage(groupId, timestamp,
|
|
||||||
out.toByteArray());
|
|
||||||
return new ForumPost(m, parent, author, contentType);
|
return new ForumPost(m, parent, author, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeAuthor(BdfWriter w, Author a) throws IOException {
|
|
||||||
w.writeListStart();
|
|
||||||
w.writeString(a.getName());
|
|
||||||
w.writeRaw(a.getPublicKey());
|
|
||||||
w.writeListEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,164 +2,114 @@ package org.briarproject.forum;
|
|||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.UniqueId;
|
import org.briarproject.api.UniqueId;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyParser;
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
import org.briarproject.api.crypto.PublicKey;
|
import org.briarproject.api.crypto.PublicKey;
|
||||||
import org.briarproject.api.crypto.Signature;
|
import org.briarproject.api.crypto.Signature;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.BdfWriter;
|
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.data.ObjectReader;
|
|
||||||
import org.briarproject.api.db.Metadata;
|
|
||||||
import org.briarproject.api.identity.Author;
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.Message;
|
|
||||||
import org.briarproject.api.sync.MessageId;
|
|
||||||
import org.briarproject.api.sync.MessageValidator;
|
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.clients.BdfMessageValidator;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
|
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
|
|
||||||
class ForumPostValidator implements MessageValidator {
|
class ForumPostValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(ForumPostValidator.class.getName());
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
private final AuthorFactory authorFactory;
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final ObjectReader<Author> authorReader;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final Clock clock;
|
|
||||||
private final KeyParser keyParser;
|
|
||||||
|
|
||||||
ForumPostValidator(CryptoComponent crypto,
|
ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory,
|
||||||
BdfReaderFactory bdfReaderFactory,
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
BdfWriterFactory bdfWriterFactory,
|
Clock clock) {
|
||||||
ObjectReader<Author> authorReader,
|
super(clientHelper, metadataEncoder, clock);
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
this.authorFactory = authorFactory;
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.authorReader = authorReader;
|
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.clock = clock;
|
|
||||||
keyParser = crypto.getSignatureKeyParser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Metadata validateMessage(Message m, Group g) {
|
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||||
// Reject the message if it's too far in the future
|
long timestamp) throws FormatException {
|
||||||
long now = clock.currentTimeMillis();
|
// Parent ID, author, content type, forum post body, signature
|
||||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
checkSize(message, 5);
|
||||||
LOG.info("Timestamp is too far in the future");
|
// Parent ID is optional
|
||||||
|
byte[] parent = message.getOptionalRaw(0);
|
||||||
|
checkLength(parent, UniqueId.LENGTH);
|
||||||
|
// Author is optional
|
||||||
|
Author author = null;
|
||||||
|
BdfList authorList = message.getOptionalList(1);
|
||||||
|
if (authorList != null) {
|
||||||
|
// Name, public key
|
||||||
|
checkSize(authorList, 2);
|
||||||
|
String name = authorList.getString(0);
|
||||||
|
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
byte[] publicKey = authorList.getRaw(1);
|
||||||
|
checkLength(publicKey, 0, MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
author = authorFactory.createAuthor(name, publicKey);
|
||||||
|
}
|
||||||
|
// Content type
|
||||||
|
String contentType = message.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);
|
||||||
|
// Signature is optional
|
||||||
|
byte[] sig = message.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) {
|
||||||
|
LOG.info("Author without signature");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
if (author == null && sig != null) {
|
||||||
// Parse the message body
|
LOG.info("Signature without author");
|
||||||
byte[] raw = m.getRaw();
|
return null;
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
}
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
// Verify the signature, if any
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
if (author != null) {
|
||||||
MessageId parent = null;
|
try {
|
||||||
Author author = null;
|
|
||||||
byte[] sig = null;
|
|
||||||
r.readListStart();
|
|
||||||
// Read the parent ID, if any
|
|
||||||
if (r.hasRaw()) {
|
|
||||||
byte[] id = r.readRaw(UniqueId.LENGTH);
|
|
||||||
if (id.length < UniqueId.LENGTH) throw new FormatException();
|
|
||||||
parent = new MessageId(id);
|
|
||||||
} else {
|
|
||||||
r.readNull();
|
|
||||||
}
|
|
||||||
// Read the author, if any
|
|
||||||
if (r.hasList()) author = authorReader.readObject(r);
|
|
||||||
else r.readNull();
|
|
||||||
// Read the content type
|
|
||||||
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
|
||||||
// Read the forum post body
|
|
||||||
byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
|
|
||||||
|
|
||||||
// Read the signature, if any
|
|
||||||
if (r.hasRaw()) sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
|
||||||
else r.readNull();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
if (author == null && sig != null) {
|
|
||||||
LOG.info("Signature without author");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Verify the signature, if any
|
|
||||||
if (author != null) {
|
|
||||||
// Parse the public key
|
// Parse the public key
|
||||||
|
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||||
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
|
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
|
||||||
// Serialise the data to be signed
|
// Serialise the data to be signed
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
BdfList signed = BdfList.of(g.getId(), timestamp, parent,
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
authorList, contentType, body);
|
||||||
w.writeListStart();
|
|
||||||
w.writeRaw(m.getGroupId().getBytes());
|
|
||||||
w.writeLong(m.getTimestamp());
|
|
||||||
if (parent == null) w.writeNull();
|
|
||||||
else w.writeRaw(parent.getBytes());
|
|
||||||
writeAuthor(w, author);
|
|
||||||
w.writeString(contentType);
|
|
||||||
w.writeRaw(postBody);
|
|
||||||
w.writeListEnd();
|
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
Signature signature = crypto.getSignature();
|
Signature signature = crypto.getSignature();
|
||||||
signature.initVerify(key);
|
signature.initVerify(key);
|
||||||
signature.update(out.toByteArray());
|
signature.update(clientHelper.toByteArray(signed));
|
||||||
if (!signature.verify(sig)) {
|
if (!signature.verify(sig)) {
|
||||||
LOG.info("Invalid signature");
|
LOG.info("Invalid signature");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
LOG.info("Invalid public key");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
// Return the metadata
|
|
||||||
BdfDictionary d = new BdfDictionary();
|
|
||||||
d.put("timestamp", m.getTimestamp());
|
|
||||||
if (parent != null) d.put("parent", parent.getBytes());
|
|
||||||
if (author != null) {
|
|
||||||
BdfDictionary d1 = new BdfDictionary();
|
|
||||||
d1.put("id", author.getId().getBytes());
|
|
||||||
d1.put("name", author.getName());
|
|
||||||
d1.put("publicKey", author.getPublicKey());
|
|
||||||
d.put("author", d1);
|
|
||||||
}
|
|
||||||
d.put("contentType", contentType);
|
|
||||||
d.put("read", false);
|
|
||||||
return metadataEncoder.encode(d);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.info("Invalid forum post");
|
|
||||||
return null;
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
LOG.info("Invalid public key");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
// Return the metadata
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
private void writeAuthor(BdfWriter w, Author a) throws IOException {
|
meta.put("timestamp", timestamp);
|
||||||
w.writeListStart();
|
if (parent != null) meta.put("parent", parent);
|
||||||
w.writeString(a.getName());
|
if (author != null) {
|
||||||
w.writeRaw(a.getPublicKey());
|
BdfDictionary authorMeta = new BdfDictionary();
|
||||||
w.writeListEnd();
|
authorMeta.put("id", author.getId());
|
||||||
|
authorMeta.put("name", author.getName());
|
||||||
|
authorMeta.put("publicKey", author.getPublicKey());
|
||||||
|
meta.put("author", authorMeta);
|
||||||
|
}
|
||||||
|
meta.put("contentType", contentType);
|
||||||
|
meta.put("read", false);
|
||||||
|
return meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ package org.briarproject.forum;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.BdfWriter;
|
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.api.data.MetadataParser;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
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;
|
||||||
@@ -27,14 +23,11 @@ import org.briarproject.api.sync.Group;
|
|||||||
import org.briarproject.api.sync.GroupFactory;
|
import org.briarproject.api.sync.GroupFactory;
|
||||||
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.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.api.sync.ValidationManager.ValidationHook;
|
import org.briarproject.api.sync.ValidationManager.ValidationHook;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -61,33 +54,23 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ForumManager forumManager;
|
private final ForumManager forumManager;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
private final GroupFactory groupFactory;
|
private final GroupFactory groupFactory;
|
||||||
private final PrivateGroupFactory privateGroupFactory;
|
private final PrivateGroupFactory privateGroupFactory;
|
||||||
private final MessageFactory messageFactory;
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final MetadataParser metadataParser;
|
|
||||||
private final SecureRandom random;
|
private final SecureRandom random;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Group localGroup;
|
private final Group localGroup;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ForumSharingManagerImpl(DatabaseComponent db,
|
ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager,
|
||||||
ForumManager forumManager, GroupFactory groupFactory,
|
ClientHelper clientHelper, GroupFactory groupFactory,
|
||||||
PrivateGroupFactory privateGroupFactory,
|
PrivateGroupFactory privateGroupFactory, SecureRandom random,
|
||||||
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
|
Clock clock) {
|
||||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
|
||||||
MetadataParser metadataParser, SecureRandom random, Clock clock) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.forumManager = forumManager;
|
this.forumManager = forumManager;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
this.groupFactory = groupFactory;
|
this.groupFactory = groupFactory;
|
||||||
this.privateGroupFactory = privateGroupFactory;
|
this.privateGroupFactory = privateGroupFactory;
|
||||||
this.messageFactory = messageFactory;
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.metadataParser = metadataParser;
|
|
||||||
this.random = random;
|
this.random = random;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
localGroup = groupFactory.createGroup(CLIENT_ID,
|
localGroup = groupFactory.createGroup(CLIENT_ID,
|
||||||
@@ -103,9 +86,9 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
db.addGroup(txn, g);
|
db.addGroup(txn, g);
|
||||||
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
|
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("contactId", c.getId().getInt());
|
meta.put("contactId", c.getId().getInt());
|
||||||
db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
|
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||||
// Share any forums that are shared with all contacts
|
// Share any forums that are shared with all contacts
|
||||||
List<Forum> shared = getForumsSharedWithAllContacts(txn);
|
List<Forum> shared = getForumsSharedWithAllContacts(txn);
|
||||||
storeMessage(txn, g.getId(), shared, 0);
|
storeMessage(txn, g.getId(), shared, 0);
|
||||||
@@ -193,8 +176,9 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
LatestUpdate latest = findLatest(txn, g.getId(), false);
|
LatestUpdate latest = findLatest(txn, g.getId(), false);
|
||||||
if (latest != null) {
|
if (latest != null) {
|
||||||
// Retrieve and parse the latest update
|
// Retrieve and parse the latest update
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn,
|
||||||
for (Forum f : parseForumList(raw)) {
|
latest.messageId);
|
||||||
|
for (Forum f : parseForumList(message)) {
|
||||||
if (!subscribed.contains(f.getGroup()))
|
if (!subscribed.contains(f.getGroup()))
|
||||||
available.add(f);
|
available.add(f);
|
||||||
}
|
}
|
||||||
@@ -321,94 +305,64 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
|
LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
|
||||||
if (latest == null) return Collections.emptyList();
|
if (latest == null) return Collections.emptyList();
|
||||||
// Retrieve and parse the latest update
|
// Retrieve and parse the latest update
|
||||||
return parseForumList(db.getRawMessage(txn, latest.messageId));
|
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
|
return parseForumList(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
LatestUpdate latest = null;
|
LatestUpdate latest = null;
|
||||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
if (d.getBoolean("local") != local) continue;
|
BdfDictionary meta = e.getValue();
|
||||||
long version = d.getLong("version");
|
if (meta.getBoolean("local") != local) continue;
|
||||||
|
long version = meta.getLong("version");
|
||||||
if (latest == null || version > latest.version)
|
if (latest == null || version > latest.version)
|
||||||
latest = new LatestUpdate(e.getKey(), version);
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
}
|
}
|
||||||
return latest;
|
return latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Forum> parseForumList(byte[] raw) throws FormatException {
|
private List<Forum> parseForumList(BdfList message) throws FormatException {
|
||||||
List<Forum> forums = new ArrayList<Forum>();
|
// Version, forum list
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
BdfList forumList = message.getList(1);
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
List<Forum> forums = new ArrayList<Forum>(forumList.size());
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
for (int i = 0; i < forumList.size(); i++) {
|
||||||
try {
|
// Name, salt
|
||||||
r.readListStart();
|
BdfList forum = forumList.getList(i);
|
||||||
r.skipLong(); // Version
|
forums.add(createForum(forum.getString(0), forum.getRaw(1)));
|
||||||
r.readListStart();
|
|
||||||
while (!r.hasListEnd()) {
|
|
||||||
r.readListStart();
|
|
||||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
|
||||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
forums.add(createForum(name, salt));
|
|
||||||
}
|
|
||||||
r.readListEnd();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return forums;
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
return forums;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(Transaction txn, GroupId g, List<Forum> forums,
|
private void storeMessage(Transaction txn, GroupId g, List<Forum> forums,
|
||||||
long version) throws DbException {
|
long version) throws DbException {
|
||||||
try {
|
try {
|
||||||
byte[] body = encodeForumList(forums, version);
|
BdfList body = encodeForumList(forums, version);
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
Message m = messageFactory.createMessage(g, now, body);
|
Message m = clientHelper.createMessage(g, now, body);
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("version", version);
|
meta.put("version", version);
|
||||||
d.put("local", true);
|
meta.put("local", true);
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, true);
|
||||||
db.addLocalMessage(txn, m, CLIENT_ID, meta, true);
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] encodeForumList(List<Forum> forums, long version) {
|
private BdfList encodeForumList(List<Forum> forums, long version) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
BdfList forumList = new BdfList();
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
for (Forum f : forums)
|
||||||
try {
|
forumList.add(BdfList.of(f.getName(), f.getSalt()));
|
||||||
w.writeListStart();
|
return BdfList.of(version, forumList);
|
||||||
w.writeLong(version);
|
|
||||||
w.writeListStart();
|
|
||||||
for (Forum f : forums) {
|
|
||||||
w.writeListStart();
|
|
||||||
w.writeString(f.getName());
|
|
||||||
w.writeRaw(f.getSalt());
|
|
||||||
w.writeListEnd();
|
|
||||||
}
|
|
||||||
w.writeListEnd();
|
|
||||||
w.writeListEnd();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
Metadata meta = db.getGroupMetadata(txn, contactGroupId);
|
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
contactGroupId);
|
||||||
return new ContactId(d.getLong("contactId").intValue());
|
return new ContactId(meta.getLong("contactId").intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<GroupId> getVisibleForums(Transaction txn,
|
private Set<GroupId> getVisibleForums(Transaction txn,
|
||||||
@@ -418,9 +372,13 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
// If there's no local update, no forums are visible
|
// If there's no local update, no forums are visible
|
||||||
if (local == null) return Collections.emptySet();
|
if (local == null) return Collections.emptySet();
|
||||||
// Intersect the sets of shared forums
|
// Intersect the sets of shared forums
|
||||||
byte[] localRaw = db.getRawMessage(txn, local.messageId);
|
BdfList localMessage = clientHelper.getMessageAsList(txn,
|
||||||
Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw));
|
local.messageId);
|
||||||
shared.retainAll(parseForumList(remoteUpdate.getRaw()));
|
Set<Forum> shared = new HashSet<Forum>(parseForumList(localMessage));
|
||||||
|
byte[] raw = remoteUpdate.getRaw();
|
||||||
|
BdfList remoteMessage = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
|
||||||
|
raw.length - MESSAGE_HEADER_LENGTH);
|
||||||
|
shared.retainAll(parseForumList(remoteMessage));
|
||||||
// Forums in the intersection should be visible
|
// Forums in the intersection should be visible
|
||||||
Set<GroupId> visible = new HashSet<GroupId>(shared.size());
|
Set<GroupId> visible = new HashSet<GroupId>(shared.size());
|
||||||
for (Forum f : shared) visible.add(f.getId());
|
for (Forum f : shared) visible.add(f.getId());
|
||||||
@@ -440,46 +398,30 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Forum createForum(String name, byte[] salt) {
|
private Forum createForum(String name, byte[] salt) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
|
||||||
try {
|
try {
|
||||||
w.writeListStart();
|
BdfList forum = BdfList.of(name, salt);
|
||||||
w.writeString(name);
|
byte[] descriptor = clientHelper.toByteArray(forum);
|
||||||
w.writeRaw(salt);
|
Group g = groupFactory.createGroup(forumManager.getClientId(),
|
||||||
w.writeListEnd();
|
descriptor);
|
||||||
} catch (IOException e) {
|
return new Forum(g, name, salt);
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
Group g = groupFactory.createGroup(forumManager.getClientId(),
|
|
||||||
out.toByteArray());
|
|
||||||
return new Forum(g, name, salt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Forum parseForum(Group g) throws FormatException {
|
private Forum parseForum(Group g) throws FormatException {
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
|
byte[] descriptor = g.getDescriptor();
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
// Name, salt
|
||||||
try {
|
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
|
||||||
r.readListStart();
|
return new Forum(g, forum.getString(0), forum.getRaw(1));
|
||||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
|
||||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return new Forum(g, name, salt);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean listContains(Transaction txn, GroupId g, GroupId forum,
|
private boolean listContains(Transaction txn, GroupId g, GroupId forum,
|
||||||
boolean local) throws DbException, FormatException {
|
boolean local) throws DbException, FormatException {
|
||||||
LatestUpdate latest = findLatest(txn, g, local);
|
LatestUpdate latest = findLatest(txn, g, local);
|
||||||
if (latest == null) return false;
|
if (latest == null) return false;
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
List<Forum> list = parseForumList(raw);
|
List<Forum> list = parseForumList(message);
|
||||||
for (Forum f : list) if (f.getId().equals(forum)) return true;
|
for (Forum f : list) if (f.getId().equals(forum)) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -491,8 +433,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
storeMessage(txn, g, Collections.singletonList(f), 0);
|
storeMessage(txn, g, Collections.singletonList(f), 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
List<Forum> list = parseForumList(raw);
|
List<Forum> list = parseForumList(message);
|
||||||
if (list.contains(f)) return false;
|
if (list.contains(f)) return false;
|
||||||
list.add(f);
|
list.add(f);
|
||||||
storeMessage(txn, g, list, latest.version + 1);
|
storeMessage(txn, g, list, latest.version + 1);
|
||||||
@@ -503,8 +445,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
|
|||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
LatestUpdate latest = findLatest(txn, g, true);
|
LatestUpdate latest = findLatest(txn, g, true);
|
||||||
if (latest == null) return;
|
if (latest == null) return;
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
|
||||||
List<Forum> list = parseForumList(raw);
|
List<Forum> list = parseForumList(message);
|
||||||
if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1);
|
if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ package org.briarproject.messaging;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.api.data.MetadataParser;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
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.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
import org.briarproject.api.messaging.MessagingManager;
|
import org.briarproject.api.messaging.MessagingManager;
|
||||||
import org.briarproject.api.messaging.PrivateMessage;
|
import org.briarproject.api.messaging.PrivateMessage;
|
||||||
@@ -27,16 +24,9 @@ import org.briarproject.api.sync.MessageId;
|
|||||||
import org.briarproject.api.sync.MessageStatus;
|
import org.briarproject.api.sync.MessageStatus;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
|
|
||||||
class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
||||||
RemoveContactHook {
|
RemoveContactHook {
|
||||||
@@ -45,25 +35,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
"6bcdc006c0910b0f44e40644c3b31f1a"
|
"6bcdc006c0910b0f44e40644c3b31f1a"
|
||||||
+ "8bf9a6d6021d40d219c86b731b903070"));
|
+ "8bf9a6d6021d40d219c86b731b903070"));
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(MessagingManagerImpl.class.getName());
|
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
private final PrivateGroupFactory privateGroupFactory;
|
private final PrivateGroupFactory privateGroupFactory;
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final MetadataParser metadataParser;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MessagingManagerImpl(DatabaseComponent db,
|
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
PrivateGroupFactory privateGroupFactory,
|
PrivateGroupFactory privateGroupFactory) {
|
||||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
|
||||||
MetadataParser metadataParser) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
this.privateGroupFactory = privateGroupFactory;
|
this.privateGroupFactory = privateGroupFactory;
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.metadataParser = metadataParser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,7 +58,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary d = new BdfDictionary();
|
||||||
d.put("contactId", c.getId().getInt());
|
d.put("contactId", c.getId().getInt());
|
||||||
db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
|
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -100,21 +81,13 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(PrivateMessage m) throws DbException {
|
public void addLocalMessage(PrivateMessage m) throws DbException {
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("timestamp", m.getMessage().getTimestamp());
|
meta.put("timestamp", m.getMessage().getTimestamp());
|
||||||
if (m.getParent() != null)
|
if (m.getParent() != null) meta.put("parent", m.getParent());
|
||||||
d.put("parent", m.getParent().getBytes());
|
meta.put("contentType", m.getContentType());
|
||||||
d.put("contentType", m.getContentType());
|
meta.put("local", true);
|
||||||
d.put("local", true);
|
meta.put("read", true);
|
||||||
d.put("read", true);
|
clientHelper.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true);
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
|
||||||
Transaction txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
db.addLocalMessage(txn, m.getMessage(), CLIENT_ID, meta, true);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -123,16 +96,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
@Override
|
@Override
|
||||||
public ContactId getContactId(GroupId g) throws DbException {
|
public ContactId getContactId(GroupId g) throws DbException {
|
||||||
try {
|
try {
|
||||||
Metadata meta;
|
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
|
||||||
Transaction txn = db.startTransaction();
|
return new ContactId(meta.getLong("contactId").intValue());
|
||||||
try {
|
|
||||||
meta = db.getGroupMetadata(txn, g);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
|
||||||
return new ContactId(d.getLong("contactId").intValue());
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -154,14 +119,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
@Override
|
@Override
|
||||||
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
|
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Map<MessageId, Metadata> metadata;
|
Map<MessageId, BdfDictionary> metadata;
|
||||||
Collection<MessageStatus> statuses;
|
Collection<MessageStatus> statuses;
|
||||||
Transaction txn = db.startTransaction();
|
Transaction txn = db.startTransaction();
|
||||||
try {
|
try {
|
||||||
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
|
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
|
||||||
metadata = db.getMessageMetadata(txn, g);
|
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
statuses = db.getMessageStatus(txn, c, g);
|
statuses = db.getMessageStatus(txn, c, g);
|
||||||
txn.setComplete();
|
txn.setComplete();
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
@@ -169,18 +136,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
new ArrayList<PrivateMessageHeader>();
|
new ArrayList<PrivateMessageHeader>();
|
||||||
for (MessageStatus s : statuses) {
|
for (MessageStatus s : statuses) {
|
||||||
MessageId id = s.getMessageId();
|
MessageId id = s.getMessageId();
|
||||||
Metadata m = metadata.get(id);
|
BdfDictionary meta = metadata.get(id);
|
||||||
if (m == null) continue;
|
if (meta == null) continue;
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = metadataParser.parse(m);
|
long timestamp = meta.getLong("timestamp");
|
||||||
long timestamp = d.getLong("timestamp");
|
String contentType = meta.getString("contentType");
|
||||||
String contentType = d.getString("contentType");
|
boolean local = meta.getBoolean("local");
|
||||||
boolean local = d.getBoolean("local");
|
boolean read = meta.getBoolean("read");
|
||||||
boolean read = d.getBoolean("read");
|
|
||||||
headers.add(new PrivateMessageHeader(id, timestamp, contentType,
|
headers.add(new PrivateMessageHeader(id, timestamp, contentType,
|
||||||
local, read, s.isSent(), s.isSeen()));
|
local, read, s.isSent(), s.isSeen()));
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
@@ -188,47 +154,21 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getMessageBody(MessageId m) throws DbException {
|
public byte[] getMessageBody(MessageId m) throws DbException {
|
||||||
byte[] raw;
|
|
||||||
Transaction txn = db.startTransaction();
|
|
||||||
try {
|
try {
|
||||||
raw = db.getRawMessage(txn, m);
|
// Parent ID, content type, private message body
|
||||||
txn.setComplete();
|
BdfList message = clientHelper.getMessageAsList(m);
|
||||||
} finally {
|
return message.getRaw(2);
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
|
||||||
try {
|
|
||||||
r.readListStart();
|
|
||||||
if (r.hasRaw()) r.skipRaw(); // Parent ID
|
|
||||||
else r.skipNull(); // No parent
|
|
||||||
r.skipString(); // Content type
|
|
||||||
byte[] messageBody = r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return messageBody;
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
public void setReadFlag(MessageId m, boolean read) throws DbException {
|
||||||
try {
|
try {
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("read", read);
|
meta.put("read", read);
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
clientHelper.mergeMessageMetadata(m, meta);
|
||||||
Transaction txn = db.startTransaction();
|
|
||||||
try {
|
|
||||||
db.mergeMessageMetadata(txn, m, meta);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package org.briarproject.messaging;
|
|||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.messaging.MessagingManager;
|
import org.briarproject.api.messaging.MessagingManager;
|
||||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||||
@@ -24,10 +24,10 @@ public class MessagingModule extends AbstractModule {
|
|||||||
|
|
||||||
@Provides @Singleton
|
@Provides @Singleton
|
||||||
PrivateMessageValidator getValidator(ValidationManager validationManager,
|
PrivateMessageValidator getValidator(ValidationManager validationManager,
|
||||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
PrivateMessageValidator validator = new PrivateMessageValidator(
|
PrivateMessageValidator validator = new PrivateMessageValidator(
|
||||||
bdfReaderFactory, metadataEncoder, clock);
|
clientHelper, metadataEncoder, clock);
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
||||||
return validator;
|
return validator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
package org.briarproject.messaging;
|
package org.briarproject.messaging;
|
||||||
|
|
||||||
import org.briarproject.api.data.BdfWriter;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.messaging.PrivateMessage;
|
import org.briarproject.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||||
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.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||||
@@ -21,36 +17,25 @@ import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESS
|
|||||||
|
|
||||||
class PrivateMessageFactoryImpl implements PrivateMessageFactory {
|
class PrivateMessageFactoryImpl implements PrivateMessageFactory {
|
||||||
|
|
||||||
private final MessageFactory messageFactory;
|
private final ClientHelper clientHelper;
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PrivateMessageFactoryImpl(MessageFactory messageFactory,
|
PrivateMessageFactoryImpl(ClientHelper clientHelper) {
|
||||||
BdfWriterFactory bdfWriterFactory) {
|
this.clientHelper = clientHelper;
|
||||||
this.messageFactory = messageFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
|
||||||
MessageId parent, String contentType, byte[] body)
|
MessageId parent, String contentType, byte[] body)
|
||||||
throws IOException, GeneralSecurityException {
|
throws FormatException {
|
||||||
// Validate the arguments
|
// Validate the arguments
|
||||||
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (body.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
|
if (body.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
// Serialise the message
|
// Serialise the message
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
BdfList message = BdfList.of(parent, contentType, body);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
||||||
w.writeListStart();
|
|
||||||
if (parent == null) w.writeNull();
|
|
||||||
else w.writeRaw(parent.getBytes());
|
|
||||||
w.writeString(contentType);
|
|
||||||
w.writeRaw(body);
|
|
||||||
w.writeListEnd();
|
|
||||||
Message m = messageFactory.createMessage(groupId, timestamp,
|
|
||||||
out.toByteArray());
|
|
||||||
return new PrivateMessage(m, parent, contentType);
|
return new PrivateMessage(m, parent, contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,83 +2,45 @@ package org.briarproject.messaging;
|
|||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.UniqueId;
|
import org.briarproject.api.UniqueId;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.db.Metadata;
|
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.Message;
|
|
||||||
import org.briarproject.api.sync.MessageId;
|
|
||||||
import org.briarproject.api.sync.MessageValidator;
|
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.clients.BdfMessageValidator;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
|
|
||||||
class PrivateMessageValidator implements MessageValidator {
|
class PrivateMessageValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
private static final Logger LOG =
|
PrivateMessageValidator(ClientHelper clientHelper,
|
||||||
Logger.getLogger(PrivateMessageValidator.class.getName());
|
|
||||||
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
|
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
super(clientHelper, metadataEncoder, clock);
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Metadata validateMessage(Message m, Group g) {
|
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||||
// Reject the message if it's too far in the future
|
long timestamp) throws FormatException {
|
||||||
long now = clock.currentTimeMillis();
|
// Parent ID, content type, private message body
|
||||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
checkSize(message, 3);
|
||||||
LOG.info("Timestamp is too far in the future");
|
// Parent ID is optional
|
||||||
return null;
|
byte[] parentId = message.getOptionalRaw(0);
|
||||||
}
|
checkLength(parentId, UniqueId.LENGTH);
|
||||||
try {
|
// Content type
|
||||||
// Parse the message body
|
String contentType = message.getString(1);
|
||||||
byte[] raw = m.getRaw();
|
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
// Private message body
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
byte[] body = message.getRaw(2);
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
checkLength(body, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||||
MessageId parent = null;
|
// Return the metadata
|
||||||
r.readListStart();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
// Read the parent ID, if any
|
meta.put("timestamp", timestamp);
|
||||||
if (r.hasRaw()) {
|
if (parentId != null) meta.put("parent", parentId);
|
||||||
byte[] id = r.readRaw(UniqueId.LENGTH);
|
meta.put("contentType", contentType);
|
||||||
if (id.length < UniqueId.LENGTH) throw new FormatException();
|
meta.put("local", false);
|
||||||
parent = new MessageId(id);
|
meta.put("read", false);
|
||||||
} else {
|
return meta;
|
||||||
r.readNull();
|
|
||||||
}
|
|
||||||
// Read the content type
|
|
||||||
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
|
||||||
// Read the private message body
|
|
||||||
r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
// Return the metadata
|
|
||||||
BdfDictionary d = new BdfDictionary();
|
|
||||||
d.put("timestamp", m.getTimestamp());
|
|
||||||
if (parent != null) d.put("parent", parent.getBytes());
|
|
||||||
d.put("contentType", contentType);
|
|
||||||
d.put("local", false);
|
|
||||||
d.put("read", false);
|
|
||||||
return metadataEncoder.encode(d);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.info("Invalid private message");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package org.briarproject.properties;
|
|||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.properties.TransportPropertyManager;
|
import org.briarproject.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.api.sync.ValidationManager;
|
import org.briarproject.api.sync.ValidationManager;
|
||||||
@@ -21,10 +21,10 @@ public class PropertiesModule extends AbstractModule {
|
|||||||
|
|
||||||
@Provides @Singleton
|
@Provides @Singleton
|
||||||
TransportPropertyValidator getValidator(ValidationManager validationManager,
|
TransportPropertyValidator getValidator(ValidationManager validationManager,
|
||||||
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
TransportPropertyValidator validator = new TransportPropertyValidator(
|
TransportPropertyValidator validator = new TransportPropertyValidator(
|
||||||
bdfReaderFactory, metadataEncoder, clock);
|
clientHelper, metadataEncoder, clock);
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
||||||
return validator;
|
return validator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,16 @@ import com.google.inject.Inject;
|
|||||||
import org.briarproject.api.DeviceId;
|
import org.briarproject.api.DeviceId;
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.TransportId;
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.clients.PrivateGroupFactory;
|
import org.briarproject.api.clients.PrivateGroupFactory;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
import org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||||
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.BdfWriter;
|
|
||||||
import org.briarproject.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
|
||||||
import org.briarproject.api.data.MetadataParser;
|
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
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.NoSuchGroupException;
|
import org.briarproject.api.db.NoSuchGroupException;
|
||||||
import org.briarproject.api.db.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
import org.briarproject.api.properties.TransportProperties;
|
import org.briarproject.api.properties.TransportProperties;
|
||||||
@@ -29,22 +24,15 @@ import org.briarproject.api.sync.Group;
|
|||||||
import org.briarproject.api.sync.GroupFactory;
|
import org.briarproject.api.sync.GroupFactory;
|
||||||
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.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
import org.briarproject.util.StringUtils;
|
import org.briarproject.util.StringUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
|
|
||||||
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||||
AddContactHook, RemoveContactHook {
|
AddContactHook, RemoveContactHook {
|
||||||
|
|
||||||
@@ -55,28 +43,18 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
|
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
private final PrivateGroupFactory privateGroupFactory;
|
private final PrivateGroupFactory privateGroupFactory;
|
||||||
private final MessageFactory messageFactory;
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final MetadataParser metadataParser;
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Group localGroup;
|
private final Group localGroup;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||||
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
|
ClientHelper clientHelper, GroupFactory groupFactory,
|
||||||
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
|
PrivateGroupFactory privateGroupFactory, Clock clock) {
|
||||||
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
|
|
||||||
MetadataParser metadataParser, Clock clock) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
this.privateGroupFactory = privateGroupFactory;
|
this.privateGroupFactory = privateGroupFactory;
|
||||||
this.messageFactory = messageFactory;
|
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.metadataParser = metadataParser;
|
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
localGroup = groupFactory.createGroup(CLIENT_ID,
|
localGroup = groupFactory.createGroup(CLIENT_ID,
|
||||||
LOCAL_GROUP_DESCRIPTOR);
|
LOCAL_GROUP_DESCRIPTOR);
|
||||||
@@ -145,8 +123,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
true);
|
true);
|
||||||
if (latest != null) {
|
if (latest != null) {
|
||||||
// Retrieve and parse the latest local properties
|
// Retrieve and parse the latest local properties
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn,
|
||||||
p = parseProperties(raw);
|
latest.messageId);
|
||||||
|
p = parseProperties(message);
|
||||||
}
|
}
|
||||||
txn.setComplete();
|
txn.setComplete();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -175,8 +154,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
|
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
|
||||||
if (latest != null) {
|
if (latest != null) {
|
||||||
// Retrieve and parse the latest remote properties
|
// Retrieve and parse the latest remote properties
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn,
|
||||||
remote.put(c.getId(), parseProperties(raw));
|
latest.messageId);
|
||||||
|
remote.put(c.getId(), parseProperties(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txn.setComplete();
|
txn.setComplete();
|
||||||
@@ -206,8 +186,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
merged = p;
|
merged = p;
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
} else {
|
||||||
byte[] raw = db.getRawMessage(txn, latest.messageId);
|
BdfList message = clientHelper.getMessageAsList(txn,
|
||||||
TransportProperties old = parseProperties(raw);
|
latest.messageId);
|
||||||
|
TransportProperties old = parseProperties(message);
|
||||||
merged = new TransportProperties(old);
|
merged = new TransportProperties(old);
|
||||||
merged.putAll(p);
|
merged.putAll(p);
|
||||||
changed = !merged.equals(old);
|
changed = !merged.equals(old);
|
||||||
@@ -250,8 +231,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
localGroup.getId(), true);
|
localGroup.getId(), true);
|
||||||
// Retrieve and parse the latest local properties
|
// Retrieve and parse the latest local properties
|
||||||
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
|
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
|
||||||
byte[] raw = db.getRawMessage(txn, e.getValue().messageId);
|
BdfList message = clientHelper.getMessageAsList(txn,
|
||||||
local.put(e.getKey(), parseProperties(raw));
|
e.getValue().messageId);
|
||||||
|
local.put(e.getKey(), parseProperties(message));
|
||||||
}
|
}
|
||||||
return local;
|
return local;
|
||||||
} catch (NoSuchGroupException e) {
|
} catch (NoSuchGroupException e) {
|
||||||
@@ -266,48 +248,35 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
TransportId t, TransportProperties p, long version, boolean local,
|
TransportId t, TransportProperties p, long version, boolean local,
|
||||||
boolean shared) throws DbException {
|
boolean shared) throws DbException {
|
||||||
try {
|
try {
|
||||||
byte[] body = encodeProperties(dev, t, p, version);
|
BdfList body = encodeProperties(dev, t, p, version);
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
Message m = messageFactory.createMessage(g, now, body);
|
Message m = clientHelper.createMessage(g, now, body);
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
d.put("transportId", t.getString());
|
meta.put("transportId", t.getString());
|
||||||
d.put("version", version);
|
meta.put("version", version);
|
||||||
d.put("local", local);
|
meta.put("local", local);
|
||||||
Metadata meta = metadataEncoder.encode(d);
|
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
|
||||||
db.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
|
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] encodeProperties(DeviceId dev, TransportId t,
|
private BdfList encodeProperties(DeviceId dev, TransportId t,
|
||||||
TransportProperties p, long version) {
|
TransportProperties p, long version) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
return BdfList.of(dev, t.getString(), version, p);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
|
||||||
try {
|
|
||||||
w.writeListStart();
|
|
||||||
w.writeRaw(dev.getBytes());
|
|
||||||
w.writeString(t.getString());
|
|
||||||
w.writeLong(version);
|
|
||||||
w.writeDictionary(p);
|
|
||||||
w.writeListEnd();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
|
private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
|
||||||
GroupId g, boolean local) throws DbException, FormatException {
|
GroupId g, boolean local) throws DbException, FormatException {
|
||||||
Map<TransportId, LatestUpdate> latestUpdates =
|
Map<TransportId, LatestUpdate> latestUpdates =
|
||||||
new HashMap<TransportId, LatestUpdate>();
|
new HashMap<TransportId, LatestUpdate>();
|
||||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
if (d.getBoolean("local") == local) {
|
BdfDictionary meta = e.getValue();
|
||||||
TransportId t = new TransportId(d.getString("transportId"));
|
if (meta.getBoolean("local") == local) {
|
||||||
long version = d.getLong("version");
|
TransportId t = new TransportId(meta.getString("transportId"));
|
||||||
|
long version = meta.getLong("version");
|
||||||
LatestUpdate latest = latestUpdates.get(t);
|
LatestUpdate latest = latestUpdates.get(t);
|
||||||
if (latest == null || version > latest.version)
|
if (latest == null || version > latest.version)
|
||||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
@@ -319,12 +288,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
||||||
boolean local) throws DbException, FormatException {
|
boolean local) throws DbException, FormatException {
|
||||||
LatestUpdate latest = null;
|
LatestUpdate latest = null;
|
||||||
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
BdfDictionary d = metadataParser.parse(e.getValue());
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
if (d.getString("transportId").equals(t.getString())
|
BdfDictionary meta = e.getValue();
|
||||||
&& d.getBoolean("local") == local) {
|
if (meta.getString("transportId").equals(t.getString())
|
||||||
long version = d.getLong("version");
|
&& meta.getBoolean("local") == local) {
|
||||||
|
long version = meta.getLong("version");
|
||||||
if (latest == null || version > latest.version)
|
if (latest == null || version > latest.version)
|
||||||
latest = new LatestUpdate(e.getKey(), version);
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
}
|
}
|
||||||
@@ -332,33 +302,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
return latest;
|
return latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportProperties parseProperties(byte[] raw)
|
private TransportProperties parseProperties(BdfList message)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
|
// Device ID, transport ID, version, properties
|
||||||
|
BdfDictionary dictionary = message.getDictionary(3);
|
||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
for (String key : dictionary.keySet())
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
p.put(key, dictionary.getString(key));
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
return p;
|
||||||
try {
|
|
||||||
r.readListStart();
|
|
||||||
r.skipRaw(); // Device ID
|
|
||||||
r.skipString(); // Transport ID
|
|
||||||
r.skipLong(); // Version
|
|
||||||
r.readDictionaryStart();
|
|
||||||
while (!r.hasDictionaryEnd()) {
|
|
||||||
String key = r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
String value = r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
p.put(key, value);
|
|
||||||
}
|
|
||||||
r.readDictionaryEnd();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
return p;
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Shouldn't happen with ByteArrayInputStream
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LatestUpdate {
|
private static class LatestUpdate {
|
||||||
|
|||||||
@@ -2,82 +2,52 @@ package org.briarproject.properties;
|
|||||||
|
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.UniqueId;
|
import org.briarproject.api.UniqueId;
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfReader;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.db.Metadata;
|
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.Message;
|
|
||||||
import org.briarproject.api.sync.MessageValidator;
|
|
||||||
import org.briarproject.api.system.Clock;
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.clients.BdfMessageValidator;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
|
||||||
|
|
||||||
class TransportPropertyValidator implements MessageValidator {
|
class TransportPropertyValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
private static final Logger LOG =
|
TransportPropertyValidator(ClientHelper clientHelper,
|
||||||
Logger.getLogger(TransportPropertyValidator.class.getName());
|
|
||||||
|
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
|
||||||
private final MetadataEncoder metadataEncoder;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
|
|
||||||
MetadataEncoder metadataEncoder, Clock clock) {
|
MetadataEncoder metadataEncoder, Clock clock) {
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
super(clientHelper, metadataEncoder, clock);
|
||||||
this.metadataEncoder = metadataEncoder;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Metadata validateMessage(Message m, Group g) {
|
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||||
// Reject the message if it's too far in the future
|
long timestamp) throws FormatException {
|
||||||
long now = clock.currentTimeMillis();
|
// Device ID, transport ID, version, properties
|
||||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
checkSize(message, 4);
|
||||||
LOG.info("Timestamp is too far in the future");
|
// Device ID
|
||||||
return null;
|
byte[] deviceId = message.getRaw(0);
|
||||||
}
|
checkLength(deviceId, UniqueId.LENGTH);
|
||||||
try {
|
// Transport ID
|
||||||
// Parse the message body
|
String transportId = message.getString(1);
|
||||||
byte[] raw = m.getRaw();
|
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
// Version
|
||||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
long version = message.getLong(2);
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
if (version < 0) throw new FormatException();
|
||||||
r.readListStart();
|
// Properties
|
||||||
byte[] deviceId = r.readRaw(UniqueId.LENGTH);
|
BdfDictionary dictionary = message.getDictionary(3);
|
||||||
if (deviceId.length != UniqueId.LENGTH) throw new FormatException();
|
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||||
String transportId = r.readString(MAX_TRANSPORT_ID_LENGTH);
|
for (String key : dictionary.keySet()) {
|
||||||
if (transportId.length() == 0) throw new FormatException();
|
checkLength(key, 0, MAX_PROPERTY_LENGTH);
|
||||||
long version = r.readLong();
|
String value = dictionary.getString(key);
|
||||||
if (version < 0) throw new FormatException();
|
checkLength(value, 0, MAX_PROPERTY_LENGTH);
|
||||||
r.readDictionaryStart();
|
|
||||||
for (int i = 0; !r.hasDictionaryEnd(); i++) {
|
|
||||||
if (i == MAX_PROPERTIES_PER_TRANSPORT)
|
|
||||||
throw new FormatException();
|
|
||||||
r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
}
|
|
||||||
r.readDictionaryEnd();
|
|
||||||
r.readListEnd();
|
|
||||||
if (!r.eof()) throw new FormatException();
|
|
||||||
// Return the metadata
|
|
||||||
BdfDictionary d = new BdfDictionary();
|
|
||||||
d.put("transportId", transportId);
|
|
||||||
d.put("version", version);
|
|
||||||
d.put("local", false);
|
|
||||||
return metadataEncoder.encode(d);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.info("Invalid transport update");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
// Return the metadata
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put("transportId", transportId);
|
||||||
|
meta.put("version", version);
|
||||||
|
meta.put("local", false);
|
||||||
|
return meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user