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:
Torsten Grote
2016-03-07 14:47:32 +00:00
20 changed files with 618 additions and 892 deletions

View File

@@ -19,6 +19,7 @@ import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.FormatException;
import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.contact.Contact;
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.util.StringUtils;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -430,9 +429,7 @@ public class ConversationActivity extends BriarActivity
try {
storeMessage(privateMessageFactory.createPrivateMessage(
groupId, timestamp, null, "text/plain", body));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (IOException e) {
} catch (FormatException e) {
throw new RuntimeException(e);
}
}

View File

@@ -23,6 +23,7 @@ import org.briarproject.android.identity.LocalAuthorItemComparator;
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
import org.briarproject.android.util.CommonLayoutParams;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
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.util.StringUtils;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -281,7 +281,7 @@ implements OnItemSelectedListener, OnClickListener {
}
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (IOException e) {
} catch (FormatException e) {
throw new RuntimeException(e);
}
storePost(p);

View File

@@ -5,6 +5,7 @@ import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
@@ -13,6 +14,13 @@ import java.util.Map;
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)
throws FormatException;
@@ -59,4 +67,13 @@ public interface ClientHelper {
void mergeMessageMetadata(Transaction txn, MessageId m,
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;
}

View File

@@ -1,21 +1,21 @@
package org.briarproject.api.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.io.IOException;
import java.security.GeneralSecurityException;
public interface ForumPostFactory {
ForumPost createAnonymousPost(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws IOException, GeneralSecurityException;
throws FormatException;
ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
MessageId parent, Author author, String contentType, byte[] body,
PrivateKey privateKey) throws IOException,
PrivateKey privateKey) throws FormatException,
GeneralSecurityException;
}

View File

@@ -1,14 +1,12 @@
package org.briarproject.api.messaging;
import org.briarproject.api.FormatException;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.io.IOException;
import java.security.GeneralSecurityException;
public interface PrivateMessageFactory {
PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws IOException, GeneralSecurityException;
throws FormatException;
}

View 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();
}
}

View File

@@ -16,6 +16,7 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
@@ -53,38 +54,35 @@ class ClientHelperImpl implements ClientHelper {
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
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
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);
return messageFactory.createMessage(g, timestamp, toByteArray(body));
}
@Override
public Message createMessage(GroupId g, long timestamp, BdfList body)
throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
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);
return messageFactory.createMessage(g, timestamp, toByteArray(body));
}
@Override
@@ -106,20 +104,8 @@ class ClientHelperImpl implements ClientHelper {
throws DbException, FormatException {
byte[] raw = db.getRawMessage(txn, m);
if (raw == null) return null;
ByteArrayInputStream in = new ByteArrayInputStream(raw,
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;
return toDictionary(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
@@ -141,20 +127,8 @@ class ClientHelperImpl implements ClientHelper {
throws DbException, FormatException {
byte[] raw = db.getRawMessage(txn, m);
if (raw == null) return null;
ByteArrayInputStream in = new ByteArrayInputStream(raw,
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;
return toList(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
@@ -259,4 +233,63 @@ class ClientHelperImpl implements ClientHelper {
BdfDictionary metadata) throws DbException, FormatException {
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);
}
}
}

View File

@@ -1,69 +1,47 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.logging.Logger;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
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.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
class ForumListValidator implements MessageValidator {
class ForumListValidator extends BdfMessageValidator {
private static final Logger LOG =
Logger.getLogger(ForumListValidator.class.getName());
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
ForumListValidator(BdfReaderFactory bdfReaderFactory,
MetadataEncoder metadataEncoder) {
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
ForumListValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
public Metadata validateMessage(Message m, Group g) {
try {
// Parse the message body
byte[] raw = m.getRaw();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart();
long version = r.readLong();
if (version < 0) throw new FormatException();
r.readListStart();
while (!r.hasListEnd()) {
r.readListStart();
String name = r.readString(MAX_FORUM_NAME_LENGTH);
if (name.length() == 0) throw new FormatException();
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
if (salt.length != 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;
public BdfDictionary validateMessage(BdfList message, Group g,
long timestamp) throws FormatException {
// Version, forum list
checkSize(message, 2);
// Version
long version = message.getLong(0);
if (version < 0) throw new FormatException();
// Forum list
BdfList forumList = message.getList(1);
for (int i = 0; i < forumList.size(); i++) {
BdfList forum = forumList.getList(i);
// Name, salt
checkSize(forum, 2);
String name = forum.getString(0);
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
byte[] salt = forum.getRaw(1);
checkLength(salt, FORUM_SALT_LENGTH);
}
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("version", version);
meta.put("local", false);
return meta;
}
}

View File

@@ -3,15 +3,12 @@ package org.briarproject.forum;
import com.google.inject.Inject;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum;
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.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -36,16 +31,10 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
class ForumManagerImpl implements ForumManager {
@@ -53,21 +42,13 @@ class ForumManagerImpl implements ForumManager {
"859a7be50dca035b64bd6902fb797097"
+ "795af837abbf8c16d750b3c2ccc186ea"));
private static final Logger LOG =
Logger.getLogger(ForumManagerImpl.class.getName());
private final DatabaseComponent db;
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser;
private final ClientHelper clientHelper;
@Inject
ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory,
MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper) {
this.db = db;
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser;
this.clientHelper = clientHelper;
}
@Override
@@ -78,29 +59,21 @@ class ForumManagerImpl implements ForumManager {
@Override
public void addLocalPost(ForumPost p) throws DbException {
try {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", p.getMessage().getTimestamp());
if (p.getParent() != null)
d.put("parent", p.getParent().getBytes());
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", p.getMessage().getTimestamp());
if (p.getParent() != null) meta.put("parent", p.getParent());
if (p.getAuthor() != null) {
Author a = p.getAuthor();
BdfDictionary d1 = new BdfDictionary();
d1.put("id", a.getId().getBytes());
d1.put("name", a.getName());
d1.put("publicKey", a.getPublicKey());
d.put("author", d1);
}
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);
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put("id", a.getId());
authorMeta.put("name", a.getName());
authorMeta.put("publicKey", a.getPublicKey());
meta.put("author", authorMeta);
}
meta.put("contentType", p.getContentType());
meta.put("local", true);
meta.put("read", true);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -145,34 +118,11 @@ class ForumManagerImpl implements ForumManager {
@Override
public byte[] getPostBody(MessageId m) throws DbException {
try {
byte[] raw;
Transaction txn = db.startTransaction();
try {
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;
// Parent ID, author, content type, forum post body, signature
BdfList message = clientHelper.getMessageAsList(m);
return message.getRaw(3);
} catch (FormatException 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 {
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, Metadata> metadata;
Map<MessageId, BdfDictionary> metadata;
Transaction txn = db.startTransaction();
try {
// Load the IDs of the user's identities
@@ -191,20 +141,22 @@ class ForumManagerImpl implements ForumManager {
for (Contact c : db.getContacts(txn))
contactAuthorIds.add(c.getAuthor().getId());
// Load the metadata
metadata = db.getMessageMetadata(txn, g);
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
try {
BdfDictionary d = metadataParser.parse(e.getValue());
long timestamp = d.getLong("timestamp");
BdfDictionary meta = entry.getValue();
long timestamp = meta.getLong("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = d.getDictionary("author", null);
BdfDictionary d1 = meta.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
@@ -216,13 +168,12 @@ class ForumManagerImpl implements ForumManager {
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = d.getString("contentType");
boolean read = d.getBoolean("read");
headers.add(new ForumPostHeader(e.getKey(), timestamp, author,
authorStatus, contentType, read));
} catch (FormatException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
String contentType = meta.getString("contentType");
boolean read = meta.getBoolean("read");
headers.add(new ForumPostHeader(entry.getKey(), timestamp,
author, authorStatus, contentType, read));
} catch (FormatException e) {
throw new DbException(e);
}
}
return headers;
@@ -231,36 +182,18 @@ class ForumManagerImpl implements ForumManager {
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary d = new BdfDictionary();
d.put("read", read);
Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
BdfDictionary meta = new BdfDictionary();
meta.put("read", read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private Forum parseForum(Group g) throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
BdfReader r = bdfReaderFactory.createReader(in);
try {
r.readListStart();
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);
}
byte[] descriptor = g.getDescriptor();
// Name, salt
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
return new Forum(g, forum.getString(0), forum.getRaw(1));
}
}

View File

@@ -3,16 +3,14 @@ package org.briarproject.forum;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.ContactManager;
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.ObjectReader;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory;
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.system.Clock;
@@ -29,13 +27,10 @@ public class ForumModule extends AbstractModule {
@Provides @Singleton
ForumPostValidator getForumPostValidator(
ValidationManager validationManager, CryptoComponent crypto,
BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory,
ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
Clock clock) {
AuthorFactory authorFactory, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ForumPostValidator validator = new ForumPostValidator(crypto,
bdfReaderFactory, bdfWriterFactory, authorReader,
metadataEncoder, clock);
authorFactory, clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(
ForumManagerImpl.CLIENT_ID, validator);
return validator;
@@ -43,11 +38,10 @@ public class ForumModule extends AbstractModule {
@Provides @Singleton
ForumListValidator getForumListValidator(
ValidationManager validationManager,
BdfReaderFactory bdfReaderFactory,
MetadataEncoder metadataEncoder) {
ForumListValidator validator = new ForumListValidator(bdfReaderFactory,
metadataEncoder);
ValidationManager validationManager, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ForumListValidator validator = new ForumListValidator(clientHelper,
metadataEncoder, clock);
validationManager.registerMessageValidator(
ForumSharingManagerImpl.CLIENT_ID, validator);
return validator;

View File

@@ -1,21 +1,19 @@
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.PrivateKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfWriter;
import org.briarproject.api.data.BdfWriterFactory;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
@@ -26,46 +24,33 @@ import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENG
class ForumPostFactoryImpl implements ForumPostFactory {
private final CryptoComponent crypto;
private final MessageFactory messageFactory;
private final BdfWriterFactory bdfWriterFactory;
private final ClientHelper clientHelper;
@Inject
ForumPostFactoryImpl(CryptoComponent crypto, MessageFactory messageFactory,
BdfWriterFactory bdfWriterFactory) {
ForumPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
this.crypto = crypto;
this.messageFactory = messageFactory;
this.bdfWriterFactory = bdfWriterFactory;
this.clientHelper = clientHelper;
}
@Override
public ForumPost createAnonymousPost(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws IOException, GeneralSecurityException {
throws FormatException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the message to a buffer
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
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());
// Serialise the message
BdfList message = BdfList.of(parent, null, contentType, body, null);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, null, contentType);
}
@Override
public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
MessageId parent, Author author, String contentType, byte[] body,
PrivateKey privateKey) throws IOException,
PrivateKey privateKey) throws FormatException,
GeneralSecurityException {
// Validate the arguments
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)
throw new IllegalArgumentException();
// Serialise the data to be signed
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
w.writeListStart();
w.writeRaw(groupId.getBytes());
w.writeLong(timestamp);
if (parent == null) w.writeNull();
else w.writeRaw(parent.getBytes());
writeAuthor(w, author);
w.writeString(contentType);
w.writeRaw(body);
w.writeListEnd();
BdfList authorList = BdfList.of(author.getName(),
author.getPublicKey());
BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
contentType, body);
// Generate the signature
Signature signature = crypto.getSignature();
signature.initSign(privateKey);
signature.update(out.toByteArray());
signature.update(clientHelper.toByteArray(signed));
byte[] sig = signature.sign();
// Serialise the signed message
out.reset();
w = bdfWriterFactory.createWriter(out);
w.writeListStart();
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());
BdfList message = BdfList.of(parent, authorList, contentType, body,
sig);
Message m = clientHelper.createMessage(groupId, timestamp, message);
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();
}
}

View File

@@ -2,164 +2,114 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.BdfWriter;
import org.briarproject.api.data.BdfWriterFactory;
import org.briarproject.api.data.BdfList;
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.AuthorFactory;
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.clients.BdfMessageValidator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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_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.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
class ForumPostValidator implements MessageValidator {
private static final Logger LOG =
Logger.getLogger(ForumPostValidator.class.getName());
class ForumPostValidator extends BdfMessageValidator {
private final CryptoComponent crypto;
private final BdfReaderFactory bdfReaderFactory;
private final BdfWriterFactory bdfWriterFactory;
private final ObjectReader<Author> authorReader;
private final MetadataEncoder metadataEncoder;
private final Clock clock;
private final KeyParser keyParser;
private final AuthorFactory authorFactory;
ForumPostValidator(CryptoComponent crypto,
BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory,
ObjectReader<Author> authorReader,
MetadataEncoder metadataEncoder, Clock clock) {
ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.crypto = crypto;
this.bdfReaderFactory = bdfReaderFactory;
this.bdfWriterFactory = bdfWriterFactory;
this.authorReader = authorReader;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
keyParser = crypto.getSignatureKeyParser();
this.authorFactory = authorFactory;
}
@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");
protected BdfDictionary validateMessage(BdfList message, Group g,
long timestamp) throws FormatException {
// Parent ID, author, content type, forum post body, signature
checkSize(message, 5);
// 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;
}
try {
// Parse the message body
byte[] raw = m.getRaw();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
MessageId parent = null;
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) {
if (author == null && sig != null) {
LOG.info("Signature without author");
return null;
}
// Verify the signature, if any
if (author != null) {
try {
// Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
// Serialise the data to be signed
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
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();
BdfList signed = BdfList.of(g.getId(), timestamp, parent,
authorList, contentType, body);
// Verify the signature
Signature signature = crypto.getSignature();
signature.initVerify(key);
signature.update(out.toByteArray());
signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) {
LOG.info("Invalid signature");
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;
}
}
private void writeAuthor(BdfWriter w, Author a) throws IOException {
w.writeListStart();
w.writeString(a.getName());
w.writeRaw(a.getPublicKey());
w.writeListEnd();
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", timestamp);
if (parent != null) meta.put("parent", parent);
if (author != null) {
BdfDictionary authorMeta = new BdfDictionary();
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;
}
}

View File

@@ -3,18 +3,14 @@ package org.briarproject.forum;
import com.google.inject.Inject;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
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.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
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.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.ValidationManager.ValidationHook;
import org.briarproject.api.system.Clock;
import org.briarproject.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
@@ -61,33 +54,23 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
private final DatabaseComponent db;
private final ForumManager forumManager;
private final ClientHelper clientHelper;
private final GroupFactory groupFactory;
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 Clock clock;
private final Group localGroup;
@Inject
ForumSharingManagerImpl(DatabaseComponent db,
ForumManager forumManager, GroupFactory groupFactory,
PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, SecureRandom random, Clock clock) {
ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager,
ClientHelper clientHelper, GroupFactory groupFactory,
PrivateGroupFactory privateGroupFactory, SecureRandom random,
Clock clock) {
this.db = db;
this.forumManager = forumManager;
this.clientHelper = clientHelper;
this.groupFactory = groupFactory;
this.privateGroupFactory = privateGroupFactory;
this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory;
this.bdfWriterFactory = bdfWriterFactory;
this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser;
this.random = random;
this.clock = clock;
localGroup = groupFactory.createGroup(CLIENT_ID,
@@ -103,9 +86,9 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
db.addGroup(txn, g);
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group
BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
BdfDictionary meta = new BdfDictionary();
meta.put("contactId", c.getId().getInt());
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
// Share any forums that are shared with all contacts
List<Forum> shared = getForumsSharedWithAllContacts(txn);
storeMessage(txn, g.getId(), shared, 0);
@@ -193,8 +176,9 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
LatestUpdate latest = findLatest(txn, g.getId(), false);
if (latest != null) {
// Retrieve and parse the latest update
byte[] raw = db.getRawMessage(txn, latest.messageId);
for (Forum f : parseForumList(raw)) {
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
for (Forum f : parseForumList(message)) {
if (!subscribed.contains(f.getGroup()))
available.add(f);
}
@@ -321,94 +305,64 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
if (latest == null) return Collections.emptyList();
// 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)
throws DbException, FormatException {
LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") != local) continue;
long version = d.getLong("version");
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getBoolean("local") != local) continue;
long version = meta.getLong("version");
if (latest == null || version > latest.version)
latest = new LatestUpdate(e.getKey(), version);
}
return latest;
}
private List<Forum> parseForumList(byte[] raw) throws FormatException {
List<Forum> forums = new ArrayList<Forum>();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
try {
r.readListStart();
r.skipLong(); // Version
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);
private List<Forum> parseForumList(BdfList message) throws FormatException {
// Version, forum list
BdfList forumList = message.getList(1);
List<Forum> forums = new ArrayList<Forum>(forumList.size());
for (int i = 0; i < forumList.size(); i++) {
// Name, salt
BdfList forum = forumList.getList(i);
forums.add(createForum(forum.getString(0), forum.getRaw(1)));
}
return forums;
}
private void storeMessage(Transaction txn, GroupId g, List<Forum> forums,
long version) throws DbException {
try {
byte[] body = encodeForumList(forums, version);
BdfList body = encodeForumList(forums, version);
long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary();
d.put("version", version);
d.put("local", true);
Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(txn, m, CLIENT_ID, meta, true);
Message m = clientHelper.createMessage(g, now, body);
BdfDictionary meta = new BdfDictionary();
meta.put("version", version);
meta.put("local", true);
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private byte[] encodeForumList(List<Forum> forums, long version) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart();
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 BdfList encodeForumList(List<Forum> forums, long version) {
BdfList forumList = new BdfList();
for (Forum f : forums)
forumList.add(BdfList.of(f.getName(), f.getSalt()));
return BdfList.of(version, forumList);
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
Metadata meta = db.getGroupMetadata(txn, contactGroupId);
BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getLong("contactId").intValue());
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
contactGroupId);
return new ContactId(meta.getLong("contactId").intValue());
}
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 (local == null) return Collections.emptySet();
// Intersect the sets of shared forums
byte[] localRaw = db.getRawMessage(txn, local.messageId);
Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw));
shared.retainAll(parseForumList(remoteUpdate.getRaw()));
BdfList localMessage = clientHelper.getMessageAsList(txn,
local.messageId);
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
Set<GroupId> visible = new HashSet<GroupId>(shared.size());
for (Forum f : shared) visible.add(f.getId());
@@ -440,46 +398,30 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
}
private Forum createForum(String name, byte[] salt) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart();
w.writeString(name);
w.writeRaw(salt);
w.writeListEnd();
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory.createGroup(forumManager.getClientId(),
descriptor);
return new Forum(g, name, salt);
} catch (FormatException 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 {
ByteArrayInputStream in = new ByteArrayInputStream(g.getDescriptor());
BdfReader r = bdfReaderFactory.createReader(in);
try {
r.readListStart();
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);
}
byte[] descriptor = g.getDescriptor();
// Name, salt
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
return new Forum(g, forum.getString(0), forum.getRaw(1));
}
private boolean listContains(Transaction txn, GroupId g, GroupId forum,
boolean local) throws DbException, FormatException {
LatestUpdate latest = findLatest(txn, g, local);
if (latest == null) return false;
byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
List<Forum> list = parseForumList(message);
for (Forum f : list) if (f.getId().equals(forum)) return true;
return false;
}
@@ -491,8 +433,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
storeMessage(txn, g, Collections.singletonList(f), 0);
return true;
}
byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
List<Forum> list = parseForumList(message);
if (list.contains(f)) return false;
list.add(f);
storeMessage(txn, g, list, latest.version + 1);
@@ -503,8 +445,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
throws DbException, FormatException {
LatestUpdate latest = findLatest(txn, g, true);
if (latest == null) return;
byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
BdfList message = clientHelper.getMessageAsList(txn, latest.messageId);
List<Forum> list = parseForumList(message);
if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1);
}

View File

@@ -3,19 +3,16 @@ package org.briarproject.messaging;
import com.google.inject.Inject;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.messaging.MessagingManager;
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.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
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,
RemoveContactHook {
@@ -45,25 +35,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
"6bcdc006c0910b0f44e40644c3b31f1a"
+ "8bf9a6d6021d40d219c86b731b903070"));
private static final Logger LOG =
Logger.getLogger(MessagingManagerImpl.class.getName());
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final PrivateGroupFactory privateGroupFactory;
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser;
@Inject
MessagingManagerImpl(DatabaseComponent db,
PrivateGroupFactory privateGroupFactory,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser) {
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
PrivateGroupFactory privateGroupFactory) {
this.db = db;
this.clientHelper = clientHelper;
this.privateGroupFactory = privateGroupFactory;
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser;
}
@Override
@@ -77,7 +58,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
// Attach the contact ID to the group
BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
clientHelper.mergeGroupMetadata(txn, g.getId(), d);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -100,21 +81,13 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override
public void addLocalMessage(PrivateMessage m) throws DbException {
try {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null)
d.put("parent", m.getParent().getBytes());
d.put("contentType", m.getContentType());
d.put("local", true);
d.put("read", 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);
}
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null) meta.put("parent", m.getParent());
meta.put("contentType", m.getContentType());
meta.put("local", true);
meta.put("read", true);
clientHelper.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -123,16 +96,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override
public ContactId getContactId(GroupId g) throws DbException {
try {
Metadata meta;
Transaction txn = db.startTransaction();
try {
meta = db.getGroupMetadata(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getLong("contactId").intValue());
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
return new ContactId(meta.getLong("contactId").intValue());
} catch (FormatException e) {
throw new DbException(e);
}
@@ -154,14 +119,16 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
throws DbException {
Map<MessageId, Metadata> metadata;
Map<MessageId, BdfDictionary> metadata;
Collection<MessageStatus> statuses;
Transaction txn = db.startTransaction();
try {
GroupId g = getContactGroup(db.getContact(txn, c)).getId();
metadata = db.getMessageMetadata(txn, g);
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
statuses = db.getMessageStatus(txn, c, g);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
@@ -169,18 +136,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
new ArrayList<PrivateMessageHeader>();
for (MessageStatus s : statuses) {
MessageId id = s.getMessageId();
Metadata m = metadata.get(id);
if (m == null) continue;
BdfDictionary meta = metadata.get(id);
if (meta == null) continue;
try {
BdfDictionary d = metadataParser.parse(m);
long timestamp = d.getLong("timestamp");
String contentType = d.getString("contentType");
boolean local = d.getBoolean("local");
boolean read = d.getBoolean("read");
long timestamp = meta.getLong("timestamp");
String contentType = meta.getString("contentType");
boolean local = meta.getBoolean("local");
boolean read = meta.getBoolean("read");
headers.add(new PrivateMessageHeader(id, timestamp, contentType,
local, read, s.isSent(), s.isSeen()));
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
throw new DbException(e);
}
}
return headers;
@@ -188,47 +154,21 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override
public byte[] getMessageBody(MessageId m) throws DbException {
byte[] raw;
Transaction txn = db.startTransaction();
try {
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);
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;
// Parent ID, content type, private message body
BdfList message = clientHelper.getMessageAsList(m);
return message.getRaw(2);
} catch (FormatException e) {
throw new DbException(e);
} catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e);
}
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary d = new BdfDictionary();
d.put("read", read);
Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
BdfDictionary meta = new BdfDictionary();
meta.put("read", read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}

View File

@@ -3,8 +3,8 @@ package org.briarproject.messaging;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessageFactory;
@@ -24,10 +24,10 @@ public class MessagingModule extends AbstractModule {
@Provides @Singleton
PrivateMessageValidator getValidator(ValidationManager validationManager,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
PrivateMessageValidator validator = new PrivateMessageValidator(
bdfReaderFactory, metadataEncoder, clock);
clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, validator);
return validator;
}

View File

@@ -1,19 +1,15 @@
package org.briarproject.messaging;
import org.briarproject.api.data.BdfWriter;
import org.briarproject.api.data.BdfWriterFactory;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.messaging.PrivateMessage;
import org.briarproject.api.messaging.PrivateMessageFactory;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
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 {
private final MessageFactory messageFactory;
private final BdfWriterFactory bdfWriterFactory;
private final ClientHelper clientHelper;
@Inject
PrivateMessageFactoryImpl(MessageFactory messageFactory,
BdfWriterFactory bdfWriterFactory) {
this.messageFactory = messageFactory;
this.bdfWriterFactory = bdfWriterFactory;
PrivateMessageFactoryImpl(ClientHelper clientHelper) {
this.clientHelper = clientHelper;
}
@Override
public PrivateMessage createPrivateMessage(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws IOException, GeneralSecurityException {
throws FormatException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the message
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
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());
BdfList message = BdfList.of(parent, contentType, body);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new PrivateMessage(m, parent, contentType);
}
}

View File

@@ -2,83 +2,45 @@ package org.briarproject.messaging;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
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.MessageId;
import org.briarproject.api.sync.MessageValidator;
import org.briarproject.api.system.Clock;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.logging.Logger;
import org.briarproject.clients.BdfMessageValidator;
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.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 =
Logger.getLogger(PrivateMessageValidator.class.getName());
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
private final Clock clock;
PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
PrivateMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
super(clientHelper, metadataEncoder, clock);
}
@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;
}
try {
// Parse the message body
byte[] raw = m.getRaw();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
MessageId parent = 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 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;
}
protected BdfDictionary validateMessage(BdfList message, Group g,
long timestamp) throws FormatException {
// Parent ID, content type, private message body
checkSize(message, 3);
// Parent ID is optional
byte[] parentId = message.getOptionalRaw(0);
checkLength(parentId, UniqueId.LENGTH);
// Content type
String contentType = message.getString(1);
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
// Private message body
byte[] body = message.getRaw(2);
checkLength(body, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", timestamp);
if (parentId != null) meta.put("parent", parentId);
meta.put("contentType", contentType);
meta.put("local", false);
meta.put("read", false);
return meta;
}
}

View File

@@ -3,8 +3,8 @@ package org.briarproject.properties;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.ValidationManager;
@@ -21,10 +21,10 @@ public class PropertiesModule extends AbstractModule {
@Provides @Singleton
TransportPropertyValidator getValidator(ValidationManager validationManager,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
TransportPropertyValidator validator = new TransportPropertyValidator(
bdfReaderFactory, metadataEncoder, clock);
clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, validator);
return validator;
}

View File

@@ -5,21 +5,16 @@ import com.google.inject.Inject;
import org.briarproject.api.DeviceId;
import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
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.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.Transaction;
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.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.system.Clock;
import org.briarproject.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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,
AddContactHook, RemoveContactHook {
@@ -55,28 +43,18 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
private final DatabaseComponent db;
private final ClientHelper clientHelper;
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 Group localGroup;
@Inject
TransportPropertyManagerImpl(DatabaseComponent db,
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, Clock clock) {
ClientHelper clientHelper, GroupFactory groupFactory,
PrivateGroupFactory privateGroupFactory, Clock clock) {
this.db = db;
this.clientHelper = clientHelper;
this.privateGroupFactory = privateGroupFactory;
this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory;
this.bdfWriterFactory = bdfWriterFactory;
this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser;
this.clock = clock;
localGroup = groupFactory.createGroup(CLIENT_ID,
LOCAL_GROUP_DESCRIPTOR);
@@ -145,8 +123,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
true);
if (latest != null) {
// Retrieve and parse the latest local properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
p = parseProperties(raw);
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
p = parseProperties(message);
}
txn.setComplete();
} finally {
@@ -175,8 +154,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
if (latest != null) {
// Retrieve and parse the latest remote properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
remote.put(c.getId(), parseProperties(raw));
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
remote.put(c.getId(), parseProperties(message));
}
}
txn.setComplete();
@@ -206,8 +186,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
merged = p;
changed = true;
} else {
byte[] raw = db.getRawMessage(txn, latest.messageId);
TransportProperties old = parseProperties(raw);
BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId);
TransportProperties old = parseProperties(message);
merged = new TransportProperties(old);
merged.putAll(p);
changed = !merged.equals(old);
@@ -250,8 +231,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
localGroup.getId(), true);
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
byte[] raw = db.getRawMessage(txn, e.getValue().messageId);
local.put(e.getKey(), parseProperties(raw));
BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId);
local.put(e.getKey(), parseProperties(message));
}
return local;
} catch (NoSuchGroupException e) {
@@ -266,48 +248,35 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
TransportId t, TransportProperties p, long version, boolean local,
boolean shared) throws DbException {
try {
byte[] body = encodeProperties(dev, t, p, version);
BdfList body = encodeProperties(dev, t, p, version);
long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary();
d.put("transportId", t.getString());
d.put("version", version);
d.put("local", local);
Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
Message m = clientHelper.createMessage(g, now, body);
BdfDictionary meta = new BdfDictionary();
meta.put("transportId", t.getString());
meta.put("version", version);
meta.put("local", local);
clientHelper.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private byte[] encodeProperties(DeviceId dev, TransportId t,
private BdfList encodeProperties(DeviceId dev, TransportId t,
TransportProperties p, long version) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
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();
return BdfList.of(dev, t.getString(), version, p);
}
private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
GroupId g, boolean local) throws DbException, FormatException {
Map<TransportId, LatestUpdate> latestUpdates =
new HashMap<TransportId, LatestUpdate>();
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") == local) {
TransportId t = new TransportId(d.getString("transportId"));
long version = d.getLong("version");
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getBoolean("local") == local) {
TransportId t = new TransportId(meta.getString("transportId"));
long version = meta.getLong("version");
LatestUpdate latest = latestUpdates.get(t);
if (latest == null || version > latest.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,
boolean local) throws DbException, FormatException {
LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getString("transportId").equals(t.getString())
&& d.getBoolean("local") == local) {
long version = d.getLong("version");
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean("local") == local) {
long version = meta.getLong("version");
if (latest == null || version > latest.version)
latest = new LatestUpdate(e.getKey(), version);
}
@@ -332,33 +302,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return latest;
}
private TransportProperties parseProperties(byte[] raw)
private TransportProperties parseProperties(BdfList message)
throws FormatException {
// Device ID, transport ID, version, properties
BdfDictionary dictionary = message.getDictionary(3);
TransportProperties p = new TransportProperties();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
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);
}
for (String key : dictionary.keySet())
p.put(key, dictionary.getString(key));
return p;
}
private static class LatestUpdate {

View File

@@ -2,82 +2,52 @@ package org.briarproject.properties;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.logging.Logger;
import org.briarproject.clients.BdfMessageValidator;
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_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 =
Logger.getLogger(TransportPropertyValidator.class.getName());
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
private final Clock clock;
TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
TransportPropertyValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
this.clock = clock;
super(clientHelper, metadataEncoder, clock);
}
@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;
}
try {
// Parse the message body
byte[] raw = m.getRaw();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart();
byte[] deviceId = r.readRaw(UniqueId.LENGTH);
if (deviceId.length != UniqueId.LENGTH) throw new FormatException();
String transportId = r.readString(MAX_TRANSPORT_ID_LENGTH);
if (transportId.length() == 0) throw new FormatException();
long version = r.readLong();
if (version < 0) throw new FormatException();
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;
protected BdfDictionary validateMessage(BdfList message, Group g,
long timestamp) throws FormatException {
// Device ID, transport ID, version, properties
checkSize(message, 4);
// Device ID
byte[] deviceId = message.getRaw(0);
checkLength(deviceId, UniqueId.LENGTH);
// Transport ID
String transportId = message.getString(1);
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
// Version
long version = message.getLong(2);
if (version < 0) throw new FormatException();
// Properties
BdfDictionary dictionary = message.getDictionary(3);
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
for (String key : dictionary.keySet()) {
checkLength(key, 0, MAX_PROPERTY_LENGTH);
String value = dictionary.getString(key);
checkLength(value, 0, MAX_PROPERTY_LENGTH);
}
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("transportId", transportId);
meta.put("version", version);
meta.put("local", false);
return meta;
}
}