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.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);
} }
} }

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;
} }

View File

@@ -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;
} }

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.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);
}
}
} }

View File

@@ -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;
} }
} }

View File

@@ -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);
}
} }
} }

View File

@@ -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;

View File

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

View File

@@ -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;
} }
} }

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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;
} }

View File

@@ -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);
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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;
} }

View File

@@ -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 {

View File

@@ -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;
} }
} }