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