mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 11:49:04 +01:00
Merge branch 'use-client-helper' into 'master'
Use client helper in existing clients Use the new ClientHelper to reduce boilerplate in existing clients. Add a BdfMessageValidator superclass for clients that format their messages as BDF lists and their metadata as BDF dictionaries (which all existing clients do). See merge request !115
This commit is contained in:
@@ -19,6 +19,7 @@ import android.widget.Toast;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
114
briar-core/src/org/briarproject/clients/BdfMessageValidator.java
Normal file
114
briar-core/src/org/briarproject/clients/BdfMessageValidator.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package org.briarproject.clients;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
public abstract class BdfMessageValidator implements MessageValidator {
|
||||
|
||||
protected static final Logger LOG =
|
||||
Logger.getLogger(BdfMessageValidator.class.getName());
|
||||
|
||||
protected final ClientHelper clientHelper;
|
||||
protected final MetadataEncoder metadataEncoder;
|
||||
protected final Clock clock;
|
||||
|
||||
protected BdfMessageValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
this.clientHelper = clientHelper;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected abstract BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException;
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
LOG.info("Timestamp is too far in the future");
|
||||
return null;
|
||||
}
|
||||
byte[] raw = m.getRaw();
|
||||
try {
|
||||
BdfList message = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
|
||||
raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfDictionary meta = validateMessage(message, g, m.getTimestamp());
|
||||
if (meta == null) {
|
||||
LOG.info("Invalid message");
|
||||
return null;
|
||||
}
|
||||
return metadataEncoder.encode(meta);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid message");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkLength(String s, int minLength, int maxLength)
|
||||
throws FormatException {
|
||||
if (s != null) {
|
||||
int length = StringUtils.toUtf8(s).length;
|
||||
if (length < minLength) throw new FormatException();
|
||||
if (length > maxLength) throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkLength(String s, int length) throws FormatException {
|
||||
if (s != null && StringUtils.toUtf8(s).length != length)
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
protected void checkLength(byte[] b, int minLength, int maxLength)
|
||||
throws FormatException {
|
||||
if (b != null) {
|
||||
if (b.length < minLength) throw new FormatException();
|
||||
if (b.length > maxLength) throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkLength(byte[] b, int length) throws FormatException {
|
||||
if (b != null && b.length != length) throw new FormatException();
|
||||
}
|
||||
|
||||
protected void checkSize(BdfList list, int minSize, int maxSize)
|
||||
throws FormatException {
|
||||
if (list != null) {
|
||||
if (list.size() < minSize) throw new FormatException();
|
||||
if (list.size() > maxSize) throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkSize(BdfList list, int size) throws FormatException {
|
||||
if (list != null && list.size() != size) throw new FormatException();
|
||||
}
|
||||
|
||||
protected void checkSize(BdfDictionary dictionary, int minSize,
|
||||
int maxSize) throws FormatException {
|
||||
if (dictionary != null) {
|
||||
if (dictionary.size() < minSize) throw new FormatException();
|
||||
if (dictionary.size() > maxSize) throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkSize(BdfDictionary dictionary, int size)
|
||||
throws FormatException {
|
||||
if (dictionary != null && dictionary.size() != size)
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageFactory;
|
||||
@@ -53,38 +54,35 @@ class ClientHelperImpl implements ClientHelper {
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
|
||||
boolean shared) throws DbException, FormatException {
|
||||
Transaction txn = db.startTransaction();
|
||||
try {
|
||||
addLocalMessage(txn, m, c, metadata, shared);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalMessage(Transaction txn, Message m, ClientId c,
|
||||
BdfDictionary metadata, boolean shared)
|
||||
throws DbException, FormatException {
|
||||
db.addLocalMessage(txn, m, c, metadataEncoder.encode(metadata), shared);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
|
||||
throws FormatException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
writer.writeDictionary(body);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
byte[] raw = out.toByteArray();
|
||||
return messageFactory.createMessage(g, timestamp, raw);
|
||||
return messageFactory.createMessage(g, timestamp, toByteArray(body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(GroupId g, long timestamp, BdfList body)
|
||||
throws FormatException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
writer.writeList(body);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
byte[] raw = out.toByteArray();
|
||||
return messageFactory.createMessage(g, timestamp, raw);
|
||||
return messageFactory.createMessage(g, timestamp, toByteArray(body));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,20 +104,8 @@ class ClientHelperImpl implements ClientHelper {
|
||||
throws DbException, FormatException {
|
||||
byte[] raw = db.getRawMessage(txn, m);
|
||||
if (raw == null) return null;
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||
BdfDictionary dictionary;
|
||||
try {
|
||||
dictionary = reader.readDictionary();
|
||||
if (!reader.eof()) throw new FormatException();
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return dictionary;
|
||||
return toDictionary(raw, MESSAGE_HEADER_LENGTH,
|
||||
raw.length - MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,20 +127,8 @@ class ClientHelperImpl implements ClientHelper {
|
||||
throws DbException, FormatException {
|
||||
byte[] raw = db.getRawMessage(txn, m);
|
||||
if (raw == null) return null;
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||
BdfList list;
|
||||
try {
|
||||
list = reader.readList();
|
||||
if (!reader.eof()) throw new FormatException();
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayInputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return list;
|
||||
return toList(raw, MESSAGE_HEADER_LENGTH,
|
||||
raw.length - MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,4 +233,63 @@ class ClientHelperImpl implements ClientHelper {
|
||||
BdfDictionary metadata) throws DbException, FormatException {
|
||||
db.mergeMessageMetadata(txn, m, metadataEncoder.encode(metadata));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray(BdfDictionary dictionary) throws FormatException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
writer.writeDictionary(dictionary);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray(BdfList list) throws FormatException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter writer = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
writer.writeList(list);
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary toDictionary(byte[] b, int off, int len)
|
||||
throws FormatException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
BdfDictionary dictionary = reader.readDictionary();
|
||||
if (!reader.eof()) throw new FormatException();
|
||||
return dictionary;
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfList toList(byte[] b, int off, int len) throws FormatException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||
BdfReader reader = bdfReaderFactory.createReader(in);
|
||||
try {
|
||||
BdfList list = reader.readList();
|
||||
if (!reader.eof()) throw new FormatException();
|
||||
return list;
|
||||
} catch (FormatException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,47 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
class ForumListValidator implements MessageValidator {
|
||||
class ForumListValidator extends BdfMessageValidator {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumListValidator.class.getName());
|
||||
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
|
||||
ForumListValidator(BdfReaderFactory bdfReaderFactory,
|
||||
MetadataEncoder metadataEncoder) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
ForumListValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
try {
|
||||
// Parse the message body
|
||||
byte[] raw = m.getRaw();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
r.readListStart();
|
||||
long version = r.readLong();
|
||||
if (version < 0) throw new FormatException();
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
String name = r.readString(MAX_FORUM_NAME_LENGTH);
|
||||
if (name.length() == 0) throw new FormatException();
|
||||
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
|
||||
if (salt.length != FORUM_SALT_LENGTH)
|
||||
throw new FormatException();
|
||||
r.readListEnd();
|
||||
}
|
||||
r.readListEnd();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("version", version);
|
||||
d.put("local", false);
|
||||
return metadataEncoder.encode(d);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Invalid forum list");
|
||||
return null;
|
||||
public BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
// Version, forum list
|
||||
checkSize(message, 2);
|
||||
// Version
|
||||
long version = message.getLong(0);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Forum list
|
||||
BdfList forumList = message.getList(1);
|
||||
for (int i = 0; i < forumList.size(); i++) {
|
||||
BdfList forum = forumList.getList(i);
|
||||
// Name, salt
|
||||
checkSize(forum, 2);
|
||||
String name = forum.getString(0);
|
||||
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
|
||||
byte[] salt = forum.getRaw(1);
|
||||
checkLength(salt, FORUM_SALT_LENGTH);
|
||||
}
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("version", version);
|
||||
meta.put("local", false);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,83 +2,45 @@ package org.briarproject.messaging;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
class PrivateMessageValidator implements MessageValidator {
|
||||
class PrivateMessageValidator extends BdfMessageValidator {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PrivateMessageValidator.class.getName());
|
||||
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
|
||||
PrivateMessageValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.clock = clock;
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
LOG.info("Timestamp is too far in the future");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Parse the message body
|
||||
byte[] raw = m.getRaw();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
MessageId parent = null;
|
||||
r.readListStart();
|
||||
// Read the parent ID, if any
|
||||
if (r.hasRaw()) {
|
||||
byte[] id = r.readRaw(UniqueId.LENGTH);
|
||||
if (id.length < UniqueId.LENGTH) throw new FormatException();
|
||||
parent = new MessageId(id);
|
||||
} else {
|
||||
r.readNull();
|
||||
}
|
||||
// Read the content type
|
||||
String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
|
||||
// Read the private message body
|
||||
r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("timestamp", m.getTimestamp());
|
||||
if (parent != null) d.put("parent", parent.getBytes());
|
||||
d.put("contentType", contentType);
|
||||
d.put("local", false);
|
||||
d.put("read", false);
|
||||
return metadataEncoder.encode(d);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Invalid private message");
|
||||
return null;
|
||||
}
|
||||
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
// Parent ID, content type, private message body
|
||||
checkSize(message, 3);
|
||||
// Parent ID is optional
|
||||
byte[] parentId = message.getOptionalRaw(0);
|
||||
checkLength(parentId, UniqueId.LENGTH);
|
||||
// Content type
|
||||
String contentType = message.getString(1);
|
||||
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||
// Private message body
|
||||
byte[] body = message.getRaw(2);
|
||||
checkLength(body, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("timestamp", timestamp);
|
||||
if (parentId != null) meta.put("parent", parentId);
|
||||
meta.put("contentType", contentType);
|
||||
meta.put("local", false);
|
||||
meta.put("read", false);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2,82 +2,52 @@ package org.briarproject.properties;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageValidator;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
|
||||
import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
class TransportPropertyValidator implements MessageValidator {
|
||||
class TransportPropertyValidator extends BdfMessageValidator {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TransportPropertyValidator.class.getName());
|
||||
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
|
||||
TransportPropertyValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.clock = clock;
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
LOG.info("Timestamp is too far in the future");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Parse the message body
|
||||
byte[] raw = m.getRaw();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw,
|
||||
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
r.readListStart();
|
||||
byte[] deviceId = r.readRaw(UniqueId.LENGTH);
|
||||
if (deviceId.length != UniqueId.LENGTH) throw new FormatException();
|
||||
String transportId = r.readString(MAX_TRANSPORT_ID_LENGTH);
|
||||
if (transportId.length() == 0) throw new FormatException();
|
||||
long version = r.readLong();
|
||||
if (version < 0) throw new FormatException();
|
||||
r.readDictionaryStart();
|
||||
for (int i = 0; !r.hasDictionaryEnd(); i++) {
|
||||
if (i == MAX_PROPERTIES_PER_TRANSPORT)
|
||||
throw new FormatException();
|
||||
r.readString(MAX_PROPERTY_LENGTH);
|
||||
r.readString(MAX_PROPERTY_LENGTH);
|
||||
}
|
||||
r.readDictionaryEnd();
|
||||
r.readListEnd();
|
||||
if (!r.eof()) throw new FormatException();
|
||||
// Return the metadata
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("transportId", transportId);
|
||||
d.put("version", version);
|
||||
d.put("local", false);
|
||||
return metadataEncoder.encode(d);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Invalid transport update");
|
||||
return null;
|
||||
protected BdfDictionary validateMessage(BdfList message, Group g,
|
||||
long timestamp) throws FormatException {
|
||||
// Device ID, transport ID, version, properties
|
||||
checkSize(message, 4);
|
||||
// Device ID
|
||||
byte[] deviceId = message.getRaw(0);
|
||||
checkLength(deviceId, UniqueId.LENGTH);
|
||||
// Transport ID
|
||||
String transportId = message.getString(1);
|
||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
// Version
|
||||
long version = message.getLong(2);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Properties
|
||||
BdfDictionary dictionary = message.getDictionary(3);
|
||||
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||
for (String key : dictionary.keySet()) {
|
||||
checkLength(key, 0, MAX_PROPERTY_LENGTH);
|
||||
String value = dictionary.getString(key);
|
||||
checkLength(value, 0, MAX_PROPERTY_LENGTH);
|
||||
}
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("transportId", transportId);
|
||||
meta.put("version", version);
|
||||
meta.put("local", false);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user