Commit 89606442 authored by Torsten Grote's avatar Torsten Grote
Browse files

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
parents 5b47d6d3 88ab694a
......@@ -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;
}
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;
}
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;
}
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;
......@@ -54,37 +55,34 @@ class ClientHelperImpl implements ClientHelper {
}
@Override
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter writer = bdfWriterFactory.createWriter(out);
public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
boolean shared) throws DbException, FormatException {
Transaction txn = db.startTransaction();
try {
writer.writeDictionary(body);
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException(e);
addLocalMessage(txn, m, c, metadata, shared);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
byte[] raw = out.toByteArray();
return messageFactory.createMessage(g, timestamp, raw);
}
@Override
public void addLocalMessage(Transaction txn, Message m, ClientId c,
BdfDictionary metadata, boolean shared)
throws DbException, FormatException {
db.addLocalMessage(txn, m, c, metadataEncoder.encode(metadata), shared);
}
@Override
public Message createMessage(GroupId g, long timestamp, BdfDictionary body)
throws FormatException {
return messageFactory.createMessage(g, timestamp, toByteArray(body));
}
@Override
public Message createMessage(GroupId g, long timestamp, BdfList body)
throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter writer = bdfWriterFactory.createWriter(out);
try {
writer.writeList(body);
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException(e);
}
byte[] raw = out.toByteArray();
return messageFactory.createMessage(g, timestamp, raw);
return messageFactory.createMessage(g, timestamp, toByteArray(body));
}
@Override
......@@ -106,20 +104,8 @@ class ClientHelperImpl implements ClientHelper {
throws DbException, FormatException {
byte[] raw = db.getRawMessage(txn, m);
if (raw == null) return null;
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader reader = bdfReaderFactory.createReader(in);
BdfDictionary dictionary;
try {
dictionary = reader.readDictionary();
if (!reader.eof()) throw new FormatException();
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e);
}
return dictionary;
return toDictionary(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
......@@ -141,20 +127,8 @@ class ClientHelperImpl implements ClientHelper {
throws DbException, FormatException {
byte[] raw = db.getRawMessage(txn, m);
if (raw == null) return null;
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader reader = bdfReaderFactory.createReader(in);
BdfList list;
try {
list = reader.readList();
if (!reader.eof()) throw new FormatException();
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e);
}
return list;
return toList(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
......@@ -259,4 +233,63 @@ class ClientHelperImpl implements ClientHelper {
BdfDictionary metadata) throws DbException, FormatException {
db.mergeMessageMetadata(txn, m, metadataEncoder.encode(metadata));
}
@Override
public byte[] toByteArray(BdfDictionary dictionary) throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter writer = bdfWriterFactory.createWriter(out);
try {
writer.writeDictionary(dictionary);
} catch (FormatException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(e);
}
return out.toByteArray();
}
@Override
public byte[] toByteArray(BdfList list) throws FormatException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter writer = bdfWriterFactory.createWriter(out);
try {
writer.writeList(list);
} catch (FormatException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(e);
}
return out.toByteArray();
}
@Override
public BdfDictionary toDictionary(byte[] b, int off, int len)
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
BdfReader reader = bdfReaderFactory.createReader(in);
try {
BdfDictionary dictionary = reader.readDictionary();
if (!reader.eof()) throw new FormatException();
return dictionary;
} catch (FormatException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public BdfList toList(byte[] b, int off, int len) throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
BdfReader reader = bdfReaderFactory.createReader(in);
try {
BdfList list = reader.readList();
if (!reader.eof()) throw new FormatException();
return list;
} catch (FormatException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageValidator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.logging.Logger;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
class ForumListValidator implements MessageValidator {
private static final Logger LOG =
Logger.getLogger(ForumListValidator.class.getName());
private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder;
class ForumListValidator extends BdfMessageValidator {
ForumListValidator(BdfReaderFactory bdfReaderFactory,
MetadataEncoder metadataEncoder) {
this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder;
ForumListValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
public Metadata validateMessage(Message m, Group g) {
try {
// Parse the message body
byte[] raw = m.getRaw();
ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart();
long version = r.readLong();
if (version < 0) throw new FormatException();
r.readListStart();
while (!r.hasListEnd()) {
r.readListStart();
String name = r.readString(MAX_FORUM_NAME_LENGTH);
if (name.length() == 0) throw new FormatException();
byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
if (salt.length != FORUM_SALT_LENGTH)
throw new FormatException();
r.readListEnd();
}
r.readListEnd();
r.readListEnd();
if (!r.eof()) throw new FormatException();
// Return the metadata
BdfDictionary d = new BdfDictionary();
d.put("version", version);
d.put("local", false);
return metadataEncoder.encode(d);
} catch (IOException e) {
LOG.info("Invalid forum list");
return null;
public BdfDictionary validateMessage(BdfList message, Group g,
long timestamp) throws FormatException {
// Version, forum list
checkSize(message, 2);
// Version
long version = message.getLong(0);
if (version < 0) throw new FormatException();
// Forum list
BdfList forumList = message.getList(1);
for (int i = 0; i < forumList.size(); i++) {
BdfList forum = forumList.getList(i);
// Name, salt
checkSize(forum, 2);
String name = forum.getString(0);
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
byte[] salt = forum.getRaw(1);
checkLength(salt, FORUM_SALT_LENGTH);
}
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("version", version);
meta.put("local", false);
return meta;
}
}
......@@ -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) {