From cc7ffee28d368fc026c5721ce467317d2908ed96 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Mon, 29 Feb 2016 21:57:02 +0000
Subject: [PATCH] Superclass for validating messages that are BDF lists.

---
 .../clients/BdfMessageValidator.java          | 114 ++++++++++
 .../forum/ForumListValidator.java             |  82 +++-----
 .../org/briarproject/forum/ForumModule.java   |  24 +--
 .../forum/ForumPostValidator.java             | 194 +++++++-----------
 .../messaging/MessagingModule.java            |   6 +-
 .../messaging/PrivateMessageValidator.java    |  92 +++------
 .../properties/PropertiesModule.java          |   6 +-
 .../TransportPropertyValidator.java           |  94 +++------
 8 files changed, 290 insertions(+), 322 deletions(-)
 create mode 100644 briar-core/src/org/briarproject/clients/BdfMessageValidator.java

diff --git a/briar-core/src/org/briarproject/clients/BdfMessageValidator.java b/briar-core/src/org/briarproject/clients/BdfMessageValidator.java
new file mode 100644
index 0000000000..fd170aa7c8
--- /dev/null
+++ b/briar-core/src/org/briarproject/clients/BdfMessageValidator.java
@@ -0,0 +1,114 @@
+package org.briarproject.clients;
+
+import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.data.BdfDictionary;
+import org.briarproject.api.data.BdfList;
+import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.db.Metadata;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageValidator;
+import org.briarproject.api.system.Clock;
+import org.briarproject.util.StringUtils;
+
+import java.util.logging.Logger;
+
+import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+
+public abstract class BdfMessageValidator implements MessageValidator {
+
+	protected static final Logger LOG =
+			Logger.getLogger(BdfMessageValidator.class.getName());
+
+	protected final ClientHelper clientHelper;
+	protected final MetadataEncoder metadataEncoder;
+	protected final Clock clock;
+
+	protected BdfMessageValidator(ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		this.clientHelper = clientHelper;
+		this.metadataEncoder = metadataEncoder;
+		this.clock = clock;
+	}
+
+	protected abstract BdfDictionary validateMessage(BdfList message, Group g,
+			long timestamp) throws FormatException;
+
+	@Override
+	public Metadata validateMessage(Message m, Group g) {
+		// Reject the message if it's too far in the future
+		long now = clock.currentTimeMillis();
+		if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
+			LOG.info("Timestamp is too far in the future");
+			return null;
+		}
+		byte[] raw = m.getRaw();
+		try {
+			BdfList message = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
+					raw.length - MESSAGE_HEADER_LENGTH);
+			BdfDictionary meta = validateMessage(message, g, m.getTimestamp());
+			if (meta == null) {
+				LOG.info("Invalid message");
+				return null;
+			}
+			return metadataEncoder.encode(meta);
+		} catch (FormatException e) {
+			LOG.info("Invalid message");
+			return null;
+		}
+	}
+
+	protected void checkLength(String s, int minLength, int maxLength)
+			throws FormatException {
+		if (s != null) {
+			int length = StringUtils.toUtf8(s).length;
+			if (length < minLength) throw new FormatException();
+			if (length > maxLength) throw new FormatException();
+		}
+	}
+
+	protected void checkLength(String s, int length) throws FormatException {
+		if (s != null && StringUtils.toUtf8(s).length != length)
+			throw new FormatException();
+	}
+
+	protected void checkLength(byte[] b, int minLength, int maxLength)
+			throws FormatException {
+		if (b != null) {
+			if (b.length < minLength) throw new FormatException();
+			if (b.length > maxLength) throw new FormatException();
+		}
+	}
+
+	protected void checkLength(byte[] b, int length) throws FormatException {
+		if (b != null && b.length != length) throw new FormatException();
+	}
+
+	protected void checkSize(BdfList list, int minSize, int maxSize)
+		throws FormatException {
+		if (list != null) {
+			if (list.size() < minSize) throw new FormatException();
+			if (list.size() > maxSize) throw new FormatException();
+		}
+	}
+
+	protected void checkSize(BdfList list, int size) throws FormatException {
+		if (list != null && list.size() != size) throw new FormatException();
+	}
+
+	protected void checkSize(BdfDictionary dictionary, int minSize,
+			int maxSize) throws FormatException {
+		if (dictionary != null) {
+			if (dictionary.size() < minSize) throw new FormatException();
+			if (dictionary.size() > maxSize) throw new FormatException();
+		}
+	}
+
+	protected void checkSize(BdfDictionary dictionary, int size)
+			throws FormatException {
+		if (dictionary != null && dictionary.size() != size)
+			throw new FormatException();
+	}
+}
diff --git a/briar-core/src/org/briarproject/forum/ForumListValidator.java b/briar-core/src/org/briarproject/forum/ForumListValidator.java
index 0a8e04e322..e4b6e4edc6 100644
--- a/briar-core/src/org/briarproject/forum/ForumListValidator.java
+++ b/briar-core/src/org/briarproject/forum/ForumListValidator.java
@@ -1,69 +1,47 @@
 package org.briarproject.forum;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageValidator;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.logging.Logger;
+import org.briarproject.api.system.Clock;
+import org.briarproject.clients.BdfMessageValidator;
 
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-
-class ForumListValidator implements MessageValidator {
-
-	private static final Logger LOG =
-			Logger.getLogger(ForumListValidator.class.getName());
 
-	private final BdfReaderFactory bdfReaderFactory;
-	private final MetadataEncoder metadataEncoder;
+class ForumListValidator extends BdfMessageValidator {
 
-	ForumListValidator(BdfReaderFactory bdfReaderFactory,
-			MetadataEncoder metadataEncoder) {
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.metadataEncoder = metadataEncoder;
+	ForumListValidator(ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		super(clientHelper, metadataEncoder, clock);
 	}
 
 	@Override
-	public Metadata validateMessage(Message m, Group g) {
-		try {
-			// Parse the message body
-			byte[] raw = m.getRaw();
-			ByteArrayInputStream in = new ByteArrayInputStream(raw,
-					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-			BdfReader r = bdfReaderFactory.createReader(in);
-			r.readListStart();
-			long version = r.readLong();
-			if (version < 0) throw new FormatException();
-			r.readListStart();
-			while (!r.hasListEnd()) {
-				r.readListStart();
-				String name = r.readString(MAX_FORUM_NAME_LENGTH);
-				if (name.length() == 0) throw new FormatException();
-				byte[] salt = r.readRaw(FORUM_SALT_LENGTH);
-				if (salt.length != FORUM_SALT_LENGTH)
-					throw new FormatException();
-				r.readListEnd();
-			}
-			r.readListEnd();
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			// Return the metadata
-			BdfDictionary d = new BdfDictionary();
-			d.put("version", version);
-			d.put("local", false);
-			return metadataEncoder.encode(d);
-		} catch (IOException e) {
-			LOG.info("Invalid forum list");
-			return null;
+	public BdfDictionary validateMessage(BdfList message, Group g,
+			long timestamp) throws FormatException {
+		// Version, forum list
+		checkSize(message, 2);
+		// Version
+		long version = message.getLong(0);
+		if (version < 0) throw new FormatException();
+		// Forum list
+		BdfList forumList = message.getList(1);
+		for (int i = 0; i < forumList.size(); i++) {
+			BdfList forum = forumList.getList(i);
+			// Name, salt
+			checkSize(forum, 2);
+			String name = forum.getString(0);
+			checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
+			byte[] salt = forum.getRaw(1);
+			checkLength(salt, FORUM_SALT_LENGTH);
 		}
+		// Return the metadata
+		BdfDictionary meta = new BdfDictionary();
+		meta.put("version", version);
+		meta.put("local", false);
+		return meta;
 	}
 }
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index caa066d344..56c18546f8 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -3,16 +3,14 @@ package org.briarproject.forum;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.data.BdfReaderFactory;
-import org.briarproject.api.data.BdfWriterFactory;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.data.ObjectReader;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
 
@@ -29,13 +27,10 @@ public class ForumModule extends AbstractModule {
 	@Provides @Singleton
 	ForumPostValidator getForumPostValidator(
 			ValidationManager validationManager, CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
-			Clock clock) {
+			AuthorFactory authorFactory, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
 		ForumPostValidator validator = new ForumPostValidator(crypto,
-				bdfReaderFactory, bdfWriterFactory, authorReader,
-				metadataEncoder, clock);
+				authorFactory, clientHelper, metadataEncoder, clock);
 		validationManager.registerMessageValidator(
 				ForumManagerImpl.CLIENT_ID, validator);
 		return validator;
@@ -43,11 +38,10 @@ public class ForumModule extends AbstractModule {
 
 	@Provides @Singleton
 	ForumListValidator getForumListValidator(
-			ValidationManager validationManager,
-			BdfReaderFactory bdfReaderFactory,
-			MetadataEncoder metadataEncoder) {
-		ForumListValidator validator = new ForumListValidator(bdfReaderFactory,
-				metadataEncoder);
+			ValidationManager validationManager, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		ForumListValidator validator = new ForumListValidator(clientHelper,
+				metadataEncoder, clock);
 		validationManager.registerMessageValidator(
 				ForumSharingManagerImpl.CLIENT_ID, validator);
 		return validator;
diff --git a/briar-core/src/org/briarproject/forum/ForumPostValidator.java b/briar-core/src/org/briarproject/forum/ForumPostValidator.java
index 3fbcf2f08f..4ea87c11f6 100644
--- a/briar-core/src/org/briarproject/forum/ForumPostValidator.java
+++ b/briar-core/src/org/briarproject/forum/ForumPostValidator.java
@@ -2,164 +2,114 @@ package org.briarproject.forum;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.UniqueId;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyParser;
 import org.briarproject.api.crypto.PublicKey;
 import org.briarproject.api.crypto.Signature;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
-import org.briarproject.api.data.BdfWriter;
-import org.briarproject.api.data.BdfWriterFactory;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.data.ObjectReader;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageId;
-import org.briarproject.api.sync.MessageValidator;
 import org.briarproject.api.system.Clock;
+import org.briarproject.clients.BdfMessageValidator;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.security.GeneralSecurityException;
-import java.util.logging.Logger;
 
 import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 
-class ForumPostValidator implements MessageValidator {
-
-	private static final Logger LOG =
-			Logger.getLogger(ForumPostValidator.class.getName());
+class ForumPostValidator extends BdfMessageValidator {
 
 	private final CryptoComponent crypto;
-	private final BdfReaderFactory bdfReaderFactory;
-	private final BdfWriterFactory bdfWriterFactory;
-	private final ObjectReader<Author> authorReader;
-	private final MetadataEncoder metadataEncoder;
-	private final Clock clock;
-	private final KeyParser keyParser;
+	private final AuthorFactory authorFactory;
 
-	ForumPostValidator(CryptoComponent crypto,
-			BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory,
-			ObjectReader<Author> authorReader,
-			MetadataEncoder metadataEncoder, Clock clock) {
+	ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
+			Clock clock) {
+		super(clientHelper, metadataEncoder, clock);
 		this.crypto = crypto;
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.bdfWriterFactory = bdfWriterFactory;
-		this.authorReader = authorReader;
-		this.metadataEncoder = metadataEncoder;
-		this.clock = clock;
-		keyParser = crypto.getSignatureKeyParser();
+		this.authorFactory = authorFactory;
 	}
 
 	@Override
-	public Metadata validateMessage(Message m, Group g) {
-		// Reject the message if it's too far in the future
-		long now = clock.currentTimeMillis();
-		if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
-			LOG.info("Timestamp is too far in the future");
+	protected BdfDictionary validateMessage(BdfList message, Group g,
+			long timestamp) throws FormatException {
+		// Parent ID, author, content type, forum post body, signature
+		checkSize(message, 5);
+		// Parent ID is optional
+		byte[] parent = message.getOptionalRaw(0);
+		checkLength(parent, UniqueId.LENGTH);
+		// Author is optional
+		Author author = null;
+		BdfList authorList = message.getOptionalList(1);
+		if (authorList != null) {
+			// Name, public key
+			checkSize(authorList, 2);
+			String name = authorList.getString(0);
+			checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
+			byte[] publicKey = authorList.getRaw(1);
+			checkLength(publicKey, 0, MAX_PUBLIC_KEY_LENGTH);
+			author = authorFactory.createAuthor(name, publicKey);
+		}
+		// Content type
+		String contentType = message.getString(2);
+		checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
+		// Forum post body
+		byte[] body = message.getRaw(3);
+		checkLength(body, 0, MAX_FORUM_POST_BODY_LENGTH);
+		// Signature is optional
+		byte[] sig = message.getOptionalRaw(4);
+		checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
+		// If there's an author there must be a signature and vice versa
+		if (author != null && sig == null) {
+			LOG.info("Author without signature");
 			return null;
 		}
-		try {
-			// Parse the message body
-			byte[] raw = m.getRaw();
-			ByteArrayInputStream in = new ByteArrayInputStream(raw,
-					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-			BdfReader r = bdfReaderFactory.createReader(in);
-			MessageId parent = null;
-			Author author = null;
-			byte[] sig = null;
-			r.readListStart();
-			// Read the parent ID, if any
-			if (r.hasRaw()) {
-				byte[] id = r.readRaw(UniqueId.LENGTH);
-				if (id.length < UniqueId.LENGTH) throw new FormatException();
-				parent = new MessageId(id);
-			} else {
-				r.readNull();
-			}
-			// Read the author, if any
-			if (r.hasList()) author = authorReader.readObject(r);
-			else r.readNull();
-			// Read the content type
-			String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
-			// Read the forum post body
-			byte[] postBody = r.readRaw(MAX_FORUM_POST_BODY_LENGTH);
-
-			// Read the signature, if any
-			if (r.hasRaw()) sig = r.readRaw(MAX_SIGNATURE_LENGTH);
-			else r.readNull();
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			// If there's an author there must be a signature and vice versa
-			if (author != null && sig == null) {
-				LOG.info("Author without signature");
-				return null;
-			}
-			if (author == null && sig != null) {
-				LOG.info("Signature without author");
-				return null;
-			}
-			// Verify the signature, if any
-			if (author != null) {
+		if (author == null && sig != null) {
+			LOG.info("Signature without author");
+			return null;
+		}
+		// Verify the signature, if any
+		if (author != null) {
+			try {
 				// Parse the public key
+				KeyParser keyParser = crypto.getSignatureKeyParser();
 				PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
 				// Serialise the data to be signed
-				ByteArrayOutputStream out = new ByteArrayOutputStream();
-				BdfWriter w = bdfWriterFactory.createWriter(out);
-				w.writeListStart();
-				w.writeRaw(m.getGroupId().getBytes());
-				w.writeLong(m.getTimestamp());
-				if (parent == null) w.writeNull();
-				else w.writeRaw(parent.getBytes());
-				writeAuthor(w, author);
-				w.writeString(contentType);
-				w.writeRaw(postBody);
-				w.writeListEnd();
+				BdfList signed = BdfList.of(g.getId(), timestamp, parentId,
+						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 (parentId != null) meta.put("parent", parentId);
+		if (author != null) {
+			BdfDictionary authorMeta = new BdfDictionary();
+			authorMeta.put("id", author.getId().getBytes());
+			authorMeta.put("name", author.getName());
+			authorMeta.put("publicKey", author.getPublicKey());
+			meta.put("author", authorMeta);
+		}
+		meta.put("contentType", contentType);
+		meta.put("read", false);
+		return meta;
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/MessagingModule.java b/briar-core/src/org/briarproject/messaging/MessagingModule.java
index fe531f715e..4ea10f9078 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingModule.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingModule.java
@@ -3,8 +3,8 @@ package org.briarproject.messaging;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.MetadataEncoder;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessageFactory;
@@ -24,10 +24,10 @@ public class MessagingModule extends AbstractModule {
 
 	@Provides @Singleton
 	PrivateMessageValidator getValidator(ValidationManager validationManager,
-			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		PrivateMessageValidator validator = new PrivateMessageValidator(
-				bdfReaderFactory, metadataEncoder, clock);
+				clientHelper, metadataEncoder, clock);
 		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
diff --git a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
index fa3e0ec5f3..0475da174b 100644
--- a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
+++ b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
@@ -2,83 +2,45 @@ package org.briarproject.messaging;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.UniqueId;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageId;
-import org.briarproject.api.sync.MessageValidator;
 import org.briarproject.api.system.Clock;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.logging.Logger;
+import org.briarproject.clients.BdfMessageValidator;
 
 import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
-
-class PrivateMessageValidator implements MessageValidator {
-
-	private static final Logger LOG =
-			Logger.getLogger(PrivateMessageValidator.class.getName());
 
-	private final BdfReaderFactory bdfReaderFactory;
-	private final MetadataEncoder metadataEncoder;
-	private final Clock clock;
+class PrivateMessageValidator extends BdfMessageValidator {
 
-	PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
+	PrivateMessageValidator(ClientHelper clientHelper,
 			MetadataEncoder metadataEncoder, Clock clock) {
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.metadataEncoder = metadataEncoder;
-		this.clock = clock;
+		super(clientHelper, metadataEncoder, clock);
 	}
 
 	@Override
-	public Metadata validateMessage(Message m, Group g) {
-		// Reject the message if it's too far in the future
-		long now = clock.currentTimeMillis();
-		if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
-			LOG.info("Timestamp is too far in the future");
-			return null;
-		}
-		try {
-			// Parse the message body
-			byte[] raw = m.getRaw();
-			ByteArrayInputStream in = new ByteArrayInputStream(raw,
-					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-			BdfReader r = bdfReaderFactory.createReader(in);
-			MessageId parent = null;
-			r.readListStart();
-			// Read the parent ID, if any
-			if (r.hasRaw()) {
-				byte[] id = r.readRaw(UniqueId.LENGTH);
-				if (id.length < UniqueId.LENGTH) throw new FormatException();
-				parent = new MessageId(id);
-			} else {
-				r.readNull();
-			}
-			// Read the content type
-			String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
-			// Read the private message body
-			r.readRaw(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			// Return the metadata
-			BdfDictionary d = new BdfDictionary();
-			d.put("timestamp", m.getTimestamp());
-			if (parent != null) d.put("parent", parent.getBytes());
-			d.put("contentType", contentType);
-			d.put("local", false);
-			d.put("read", false);
-			return metadataEncoder.encode(d);
-		} catch (IOException e) {
-			LOG.info("Invalid private message");
-			return null;
-		}
+	protected BdfDictionary validateMessage(BdfList message, Group g,
+			long timestamp) throws FormatException {
+		// Parent ID, content type, private message body
+		checkSize(message, 3);
+		// Parent ID is optional
+		byte[] parentId = message.getOptionalRaw(0);
+		checkLength(parentId, UniqueId.LENGTH);
+		// Content type
+		String contentType = message.getString(1);
+		checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
+		// Private message body
+		byte[] body = message.getRaw(2);
+		checkLength(body, 0, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
+		// Return the metadata
+		BdfDictionary meta = new BdfDictionary();
+		meta.put("timestamp", timestamp);
+		if (parentId != null) meta.put("parent", parentId);
+		meta.put("contentType", contentType);
+		meta.put("local", false);
+		meta.put("read", false);
+		return meta;
 	}
 }
diff --git a/briar-core/src/org/briarproject/properties/PropertiesModule.java b/briar-core/src/org/briarproject/properties/PropertiesModule.java
index 9edbf7e16a..cd40134deb 100644
--- a/briar-core/src/org/briarproject/properties/PropertiesModule.java
+++ b/briar-core/src/org/briarproject/properties/PropertiesModule.java
@@ -3,8 +3,8 @@ package org.briarproject.properties;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.MetadataEncoder;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.ValidationManager;
@@ -21,10 +21,10 @@ public class PropertiesModule extends AbstractModule {
 
 	@Provides @Singleton
 	TransportPropertyValidator getValidator(ValidationManager validationManager,
-			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
+			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		TransportPropertyValidator validator = new TransportPropertyValidator(
-				bdfReaderFactory, metadataEncoder, clock);
+				clientHelper, metadataEncoder, clock);
 		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
index 06973ae7d8..e6fcd69a6c 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyValidator.java
@@ -2,82 +2,52 @@ package org.briarproject.properties;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.UniqueId;
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.data.BdfDictionary;
-import org.briarproject.api.data.BdfReader;
-import org.briarproject.api.data.BdfReaderFactory;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.db.Metadata;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageValidator;
 import org.briarproject.api.system.Clock;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.logging.Logger;
+import org.briarproject.clients.BdfMessageValidator;
 
 import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
 import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
 import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
-import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
-
-class TransportPropertyValidator implements MessageValidator {
 
-	private static final Logger LOG =
-			Logger.getLogger(TransportPropertyValidator.class.getName());
+class TransportPropertyValidator extends BdfMessageValidator {
 
-	private final BdfReaderFactory bdfReaderFactory;
-	private final MetadataEncoder metadataEncoder;
-	private final Clock clock;
-
-	TransportPropertyValidator(BdfReaderFactory bdfReaderFactory,
+	TransportPropertyValidator(ClientHelper clientHelper,
 			MetadataEncoder metadataEncoder, Clock clock) {
-		this.bdfReaderFactory = bdfReaderFactory;
-		this.metadataEncoder = metadataEncoder;
-		this.clock = clock;
+		super(clientHelper, metadataEncoder, clock);
 	}
 
 	@Override
-	public Metadata validateMessage(Message m, Group g) {
-		// Reject the message if it's too far in the future
-		long now = clock.currentTimeMillis();
-		if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
-			LOG.info("Timestamp is too far in the future");
-			return null;
-		}
-		try {
-			// Parse the message body
-			byte[] raw = m.getRaw();
-			ByteArrayInputStream in = new ByteArrayInputStream(raw,
-					MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
-			BdfReader r = bdfReaderFactory.createReader(in);
-			r.readListStart();
-			byte[] deviceId = r.readRaw(UniqueId.LENGTH);
-			if (deviceId.length != UniqueId.LENGTH) throw new FormatException();
-			String transportId = r.readString(MAX_TRANSPORT_ID_LENGTH);
-			if (transportId.length() == 0) throw new FormatException();
-			long version = r.readLong();
-			if (version < 0) throw new FormatException();
-			r.readDictionaryStart();
-			for (int i = 0; !r.hasDictionaryEnd(); i++) {
-				if (i == MAX_PROPERTIES_PER_TRANSPORT)
-					throw new FormatException();
-				r.readString(MAX_PROPERTY_LENGTH);
-				r.readString(MAX_PROPERTY_LENGTH);
-			}
-			r.readDictionaryEnd();
-			r.readListEnd();
-			if (!r.eof()) throw new FormatException();
-			// Return the metadata
-			BdfDictionary d = new BdfDictionary();
-			d.put("transportId", transportId);
-			d.put("version", version);
-			d.put("local", false);
-			return metadataEncoder.encode(d);
-		} catch (IOException e) {
-			LOG.info("Invalid transport update");
-			return null;
+	protected BdfDictionary validateMessage(BdfList message, Group g,
+			long timestamp) throws FormatException {
+		// Device ID, transport ID, version, properties
+		checkSize(message, 4);
+		// Device ID
+		byte[] deviceId = message.getRaw(0);
+		checkLength(deviceId, UniqueId.LENGTH);
+		// Transport ID
+		String transportId = message.getString(1);
+		checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
+		// Version
+		long version = message.getLong(2);
+		if (version < 0) throw new FormatException();
+		// Properties
+		BdfDictionary dictionary = message.getDictionary(3);
+		checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
+		for (String key : dictionary.keySet()) {
+			checkLength(key, 0, MAX_PROPERTY_LENGTH);
+			String value = dictionary.getString(key);
+			checkLength(value, 0, MAX_PROPERTY_LENGTH);
 		}
+		// Return the metadata
+		BdfDictionary meta = new BdfDictionary();
+		meta.put("transportId", transportId);
+		meta.put("version", version);
+		meta.put("local", false);
+		return meta;
 	}
 }
-- 
GitLab