Separate the sync layer from its clients. #112

This commit is contained in:
akwizgran
2015-12-21 14:36:24 +00:00
parent f5f572139a
commit 5355951466
117 changed files with 3160 additions and 3465 deletions

View File

@@ -0,0 +1,20 @@
package org.briarproject.api.sync;
import org.briarproject.api.UniqueId;
import java.util.Arrays;
/**
* Type-safe wrapper for a byte array that uniquely identifies a sync client.
*/
public class ClientId extends UniqueId {
public ClientId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
return o instanceof ClientId && Arrays.equals(id, ((ClientId) o).id);
}
}

View File

@@ -1,28 +1,20 @@
package org.briarproject.api.sync;
import java.io.UnsupportedEncodingException;
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
/** A group to which users may subscribe. */
public class Group {
private final GroupId id;
private final String name;
private final byte[] salt;
private final ClientId clientId;
private final byte[] descriptor;
public Group(GroupId id, String name, byte[] salt) {
int length;
try {
length = name.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
if (length == 0 || length > MessagingConstants.MAX_GROUP_NAME_LENGTH)
throw new IllegalArgumentException();
if (salt.length != MessagingConstants.GROUP_SALT_LENGTH)
public Group(GroupId id, ClientId clientId, byte[] descriptor) {
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
throw new IllegalArgumentException();
this.id = id;
this.name = name;
this.salt = salt;
this.clientId = clientId;
this.descriptor = descriptor;
}
/** Returns the group's unique identifier. */
@@ -30,17 +22,14 @@ public class Group {
return id;
}
/** Returns the group's name. */
public String getName() {
return name;
/** Returns the ID of the client to which the group belongs. */
public ClientId getClientId() {
return clientId;
}
/**
* Returns the salt used to distinguish the group from other groups with
* the same name.
*/
public byte[] getSalt() {
return salt;
/** Returns the group's descriptor. */
public byte[] getDescriptor() {
return descriptor;
}
@Override

View File

@@ -2,9 +2,6 @@ package org.briarproject.api.sync;
public interface GroupFactory {
/** Creates a group with the given name and a random salt. */
Group createGroup(String name);
/** Creates a group with the given name and salt. */
Group createGroup(String name, byte[] salt);
/** Creates a group with the given client ID and descriptor. */
Group createGroup(ClientId c, byte[] descriptor);
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.api.sync;
import org.briarproject.api.UniqueId;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
@@ -9,14 +10,16 @@ import java.util.Arrays;
*/
public class GroupId extends UniqueId {
/** Label for hashing groups to calculate their identifiers. */
public static final byte[] LABEL =
"GROUP_ID".getBytes(Charset.forName("US-ASCII"));
public GroupId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
if (o instanceof GroupId)
return Arrays.equals(id, ((GroupId) o).id);
return false;
return o instanceof GroupId && Arrays.equals(id, ((GroupId) o).id);
}
}

View File

@@ -1,42 +1,58 @@
package org.briarproject.api.sync;
import org.briarproject.api.identity.Author;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
public interface Message {
public class Message {
private final MessageId id;
private final GroupId groupId;
private final long timestamp;
private final byte[] raw;
public Message(MessageId id, GroupId groupId, long timestamp, byte[] raw) {
if (raw.length <= MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException();
if (raw.length > MAX_MESSAGE_LENGTH)
throw new IllegalArgumentException();
this.id = id;
this.groupId = groupId;
this.timestamp = timestamp;
this.raw = raw;
}
/** Returns the message's unique identifier. */
MessageId getId();
public MessageId getId() {
return id;
}
/**
* Returns the identifier of the message's parent, or null if this is the
* first message in a thread.
*/
MessageId getParent();
/**
* Returns the {@link Group} to which the message belongs, or null if this
* is a private message.
*/
Group getGroup();
/**
* Returns the message's {@link Author Author}, or null
* if this is an anonymous message.
*/
Author getAuthor();
/** Returns the message's content type. */
String getContentType();
/** Returns the ID of the {@link Group} to which the message belongs. */
public GroupId getGroupId() {
return groupId;
}
/** Returns the message's timestamp in milliseconds since the Unix epoch. */
long getTimestamp();
public long getTimestamp() {
return timestamp;
}
/** Returns the serialised message. */
byte[] getSerialised();
/** Returns the length of the raw message in bytes. */
public int getLength() {
return raw.length;
}
/** Returns the offset of the message body within the serialised message. */
int getBodyStart();
/** Returns the raw message. */
public byte[] getRaw() {
return raw;
}
/** Returns the length of the message body in bytes. */
int getBodyLength();
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof Message && id.equals(((Message) o).getId());
}
}

View File

@@ -1,19 +1,9 @@
package org.briarproject.api.sync;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.identity.Author;
import java.io.IOException;
import java.security.GeneralSecurityException;
public interface MessageFactory {
Message createAnonymousMessage(MessageId parent, Group group,
String contentType, long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
Message createPseudonymousMessage(MessageId parent, Group group,
Author author, PrivateKey privateKey, String contentType,
long timestamp, byte[] body) throws IOException,
GeneralSecurityException;
Message createMessage(GroupId groupId, long timestamp, byte[] body)
throws IOException;
}

View File

@@ -1,92 +0,0 @@
package org.briarproject.api.sync;
import org.briarproject.api.identity.Author;
public class MessageHeader {
public enum State { STORED, SENT, DELIVERED }
private final MessageId id, parent;
private final GroupId groupId;
private final Author author;
private final Author.Status authorStatus;
private final String contentType;
private final long timestamp;
private final boolean local, read;
private final State status;
public MessageHeader(MessageId id, MessageId parent, GroupId groupId,
Author author, Author.Status authorStatus, String contentType,
long timestamp, boolean local, boolean read, State status) {
this.id = id;
this.parent = parent;
this.groupId = groupId;
this.author = author;
this.authorStatus = authorStatus;
this.contentType = contentType;
this.timestamp = timestamp;
this.local = local;
this.read = read;
this.status = status;
}
/** Returns the message's unique identifier. */
public MessageId getId() {
return id;
}
/**
* Returns the message's parent, or null if this is the first message in a
* thread.
*/
public MessageId getParent() {
return parent;
}
/**
* Returns the unique identifier of the group to which the message belongs.
*/
public GroupId getGroupId() {
return groupId;
}
/**
* Returns the message's author, or null if this is an anonymous message.
*/
public Author getAuthor() {
return author;
}
/** Returns the status of the message's author. */
public Author.Status getAuthorStatus() {
return authorStatus;
}
/** Returns the message's content type. */
public String getContentType() {
return contentType;
}
/** Returns the timestamp created by the message's author. */
public long getTimestamp() {
return timestamp;
}
/** Returns true if the message was locally generated. */
public boolean isLocal() {
return local;
}
/** Returns true if the message has been read. */
public boolean isRead() {
return read;
}
/**
* Returns message status. (This only applies to locally generated private
* messages.)
*/
public State getStatus() {
return status;
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.api.sync;
import org.briarproject.api.UniqueId;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
@@ -10,14 +11,16 @@ import java.util.Arrays;
*/
public class MessageId extends UniqueId {
/** Label for hashing messages to calculate their identifiers. */
public static final byte[] LABEL =
"MESSAGE_ID".getBytes(Charset.forName("US-ASCII"));
public MessageId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
if (o instanceof MessageId)
return Arrays.equals(id, ((MessageId) o).id);
return false;
return o instanceof MessageId && Arrays.equals(id, ((MessageId) o).id);
}
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.api.sync;
import org.briarproject.api.contact.ContactId;
public class MessageStatus {
private final MessageId messageId;
private final ContactId contactId;
private final boolean sent, seen;
public MessageStatus(MessageId messageId, ContactId contactId,
boolean sent, boolean seen) {
this.messageId = messageId;
this.contactId = contactId;
this.sent = sent;
this.seen = seen;
}
/** Returns the ID of the message. */
public MessageId getMessageId() {
return messageId;
}
/** Returns the ID of the contact. */
public ContactId getContactId() {
return contactId;
}
/** Returns true if the message has been sent to the contact. */
public boolean isSent() {
return sent;
}
/** Returns true if the message has been seen by the contact. */
public boolean isSeen() {
return seen;
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.api.sync;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.lifecycle.Service;
public interface MessageValidator extends Service {
/**
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
*/
Metadata validateMessage(Message m);
}

View File

@@ -1,9 +0,0 @@
package org.briarproject.api.sync;
import java.security.GeneralSecurityException;
/** Verifies the signatures on an {@link UnverifiedMessage}. */
public interface MessageVerifier {
Message verifyMessage(UnverifiedMessage m) throws GeneralSecurityException;
}

View File

@@ -1,46 +0,0 @@
package org.briarproject.api.sync;
public interface MessagingConstants {
/** The current version of the messaging protocol. */
byte PROTOCOL_VERSION = 0;
/** The length of the packet header in bytes. */
int HEADER_LENGTH = 4;
/** The maximum length of the packet payload in bytes. */
int MAX_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
/** The maximum number of public groups a user may subscribe to. */
int MAX_SUBSCRIPTIONS = 300;
/** The maximum length of a group's name in UTF-8 bytes. */
int MAX_GROUP_NAME_LENGTH = 50;
/** The length of a group's random salt in bytes. */
int GROUP_SALT_LENGTH = 32;
/**
* The maximum length of a message body in bytes. To allow for future
* changes in the protocol, this is smaller than the maximum payload length
* even when all the message's other fields have their maximum lengths.
*/
int MAX_BODY_LENGTH = MAX_PAYLOAD_LENGTH - 1024;
/** The maximum length of a message's content type in UTF-8 bytes. */
int MAX_CONTENT_TYPE_LENGTH = 50;
/** The maximum length of a message's subject line in UTF-8 bytes. */
int MAX_SUBJECT_LENGTH = 100;
/** The length of a message's random salt in bytes. */
int MESSAGE_SALT_LENGTH = 32;
/**
* When calculating the retention time of the database, the timestamp of
* the oldest message in the database is rounded down to a multiple of
* this value to avoid revealing the presence of any particular message.
*/
int RETENTION_GRANULARITY = 60 * 1000; // 1 minute
}

View File

@@ -10,7 +10,7 @@ public interface PacketReader {
Ack readAck() throws IOException;
boolean hasMessage() throws IOException;
UnverifiedMessage readMessage() throws IOException;
Message readMessage() throws IOException;
boolean hasOffer() throws IOException;
Offer readOffer() throws IOException;

View File

@@ -1,6 +1,6 @@
package org.briarproject.api.sync;
/** Packet types for the messaging protocol. */
/** Packet types for the sync protocol. */
public interface PacketTypes {
byte ACK = 0;

View File

@@ -0,0 +1,30 @@
package org.briarproject.api.sync;
import org.briarproject.api.UniqueId;
public interface SyncConstants {
/** The current version of the sync protocol. */
byte PROTOCOL_VERSION = 0;
/** The length of the packet header in bytes. */
int PACKET_HEADER_LENGTH = 4;
/** The maximum length of the packet payload in bytes. */
int MAX_PACKET_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
/** The maximum number of groups a user may subscribe to. */
int MAX_SUBSCRIPTIONS = 200;
/** The maximum length of a group descriptor in bytes. */
int MAX_GROUP_DESCRIPTOR_LENGTH = 100;
/** The maximum length of a message in bytes. */
int MAX_MESSAGE_LENGTH = MAX_PACKET_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH;
/** The length of the message header in bytes. */
int MESSAGE_HEADER_LENGTH = UniqueId.LENGTH + 8;
/** The maximum length of a message body in bytes. */
int MAX_MESSAGE_BODY_LENGTH = MAX_MESSAGE_LENGTH - MESSAGE_HEADER_LENGTH;
}

View File

@@ -2,7 +2,7 @@ package org.briarproject.api.sync;
import java.io.IOException;
public interface MessagingSession {
public interface SyncSession {
/**
* Runs the session. This method returns when there are no more packets to

View File

@@ -6,14 +6,14 @@ import org.briarproject.api.contact.ContactId;
import java.io.InputStream;
import java.io.OutputStream;
public interface MessagingSessionFactory {
public interface SyncSessionFactory {
MessagingSession createIncomingSession(ContactId c, TransportId t,
SyncSession createIncomingSession(ContactId c, TransportId t,
InputStream in);
MessagingSession createSimplexOutgoingSession(ContactId c, TransportId t,
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, OutputStream out);
MessagingSession createDuplexOutgoingSession(ContactId c, TransportId t,
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, OutputStream out);
}

View File

@@ -1,94 +0,0 @@
package org.briarproject.api.sync;
import org.briarproject.api.identity.Author;
/** A {@link Message} that has not yet had its signatures (if any) verified. */
public class UnverifiedMessage {
private final MessageId parent;
private final Group group;
private final Author author;
private final String contentType;
private final long timestamp;
private final byte[] raw, signature;
private final int bodyStart, bodyLength, signedLength;
public UnverifiedMessage(MessageId parent, Group group, Author author,
String contentType, long timestamp, byte[] raw, byte[] signature,
int bodyStart, int bodyLength, int signedLength) {
this.parent = parent;
this.group = group;
this.author = author;
this.contentType = contentType;
this.timestamp = timestamp;
this.raw = raw;
this.signature = signature;
this.bodyStart = bodyStart;
this.bodyLength = bodyLength;
this.signedLength = signedLength;
}
/**
* Returns the identifier of the message's parent, or null if this is the
* first message in a thread.
*/
public MessageId getParent() {
return parent;
}
/**
* Returns the {@link Group} to which the message belongs, or null if this
* is a private message.
*/
public Group getGroup() {
return group;
}
/**
* Returns the message's {@link Author Author}, or null
* if this is an anonymous message.
*/
public Author getAuthor() {
return author;
}
/** Returns the message's content type. */
public String getContentType() {
return contentType;
}
/** Returns the message's timestamp. */
public long getTimestamp() {
return timestamp;
}
/** Returns the serialised message. */
public byte[] getSerialised() {
return raw;
}
/**
* Returns the author's signature, or null if this is an anonymous message.
*/
public byte[] getSignature() {
return signature;
}
/** Returns the offset of the message body within the serialised message. */
public int getBodyStart() {
return bodyStart;
}
/** Returns the length of the message body in bytes. */
public int getBodyLength() {
return bodyLength;
}
/**
* Returns the length in bytes of the data covered by the author's
* signature.
*/
public int getSignedLength() {
return signedLength;
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.api.sync;
import org.briarproject.api.lifecycle.Service;
/**
* Responsible for managing message validators and passing them messages to
* validate.
*/
public interface ValidationManager extends Service {
/** Sets the message validator for the given client. */
void setMessageValidator(ClientId c, MessageValidator v);
}