Separated the subject line from the message body.

This commit is contained in:
akwizgran
2011-10-21 18:25:25 +01:00
parent 4d23e14d89
commit f2d80825bc
12 changed files with 95 additions and 54 deletions

View File

@@ -10,6 +10,9 @@ public interface Message {
static final int MAX_BODY_LENGTH =
ProtocolConstants.MAX_PACKET_LENGTH - 1024;
/** The maximum length of a subject line in UTF-8 bytes. */
static final int MAX_SUBJECT_LENGTH = 100;
/** The maximum length of a signature in bytes. */
static final int MAX_SIGNATURE_LENGTH = 100;
@@ -31,6 +34,9 @@ public interface Message {
/** Returns the message's author. */
AuthorId getAuthor();
/** Returns the message's subject line. */
String getSubject();
/** Returns the timestamp created by the message's author. */
long getTimestamp();

View File

@@ -7,24 +7,25 @@ import java.security.PrivateKey;
public interface MessageEncoder {
/** Encodes a private message. */
Message encodeMessage(MessageId parent, byte[] body) throws IOException,
GeneralSecurityException;
Message encodeMessage(MessageId parent, String subject, byte[] body)
throws IOException, GeneralSecurityException;
/** Encodes an anonymous message to an unrestricted group. */
Message encodeMessage(MessageId parent, Group group, byte[] body)
throws IOException, GeneralSecurityException;
Message encodeMessage(MessageId parent, Group group, String subject,
byte[] body) throws IOException, GeneralSecurityException;
/** Encodes an anonymous message to a restricted group. */
Message encodeMessage(MessageId parent, Group group, PrivateKey groupKey,
byte[] body) throws IOException, GeneralSecurityException;
String subject, byte[] body) throws IOException,
GeneralSecurityException;
/** Encodes a pseudonymous message to an unrestricted group. */
Message encodeMessage(MessageId parent, Group group, Author author,
PrivateKey authorKey, byte[] body) throws IOException,
GeneralSecurityException;
PrivateKey authorKey, String subject, byte[] body)
throws IOException, GeneralSecurityException;
/** Encode a pseudonymous message to a restricted group. */
Message encodeMessage(MessageId parent, Group group, PrivateKey groupKey,
Author author, PrivateKey authorKey, byte[] body)
Author author, PrivateKey authorKey, String subject, byte[] body)
throws IOException, GeneralSecurityException;
}

View File

@@ -39,37 +39,42 @@ class MessageEncoderImpl implements MessageEncoder {
this.writerFactory = writerFactory;
}
public Message encodeMessage(MessageId parent, byte[] body)
public Message encodeMessage(MessageId parent, String subject, byte[] body)
throws IOException, GeneralSecurityException {
return encodeMessage(parent, null, null, null, null, body);
return encodeMessage(parent, null, null, null, null, subject, body);
}
public Message encodeMessage(MessageId parent, Group group, byte[] body)
throws IOException, GeneralSecurityException {
return encodeMessage(parent, group, null, null, null, body);
public Message encodeMessage(MessageId parent, Group group, String subject,
byte[] body) throws IOException, GeneralSecurityException {
return encodeMessage(parent, group, null, null, null, subject, body);
}
public Message encodeMessage(MessageId parent, Group group,
PrivateKey groupKey, byte[] body) throws IOException,
GeneralSecurityException {
return encodeMessage(parent, group, groupKey, null, null, body);
PrivateKey groupKey, String subject, byte[] body)
throws IOException, GeneralSecurityException {
return encodeMessage(parent, group, groupKey, null, null, subject,
body);
}
public Message encodeMessage(MessageId parent, Group group, Author author,
PrivateKey authorKey, byte[] body) throws IOException,
GeneralSecurityException {
return encodeMessage(parent, group, null, author, authorKey, body);
PrivateKey authorKey, String subject, byte[] body)
throws IOException, GeneralSecurityException {
return encodeMessage(parent, group, null, author, authorKey, subject,
body);
}
public Message encodeMessage(MessageId parent, Group group,
PrivateKey groupKey, Author author, PrivateKey authorKey,
byte[] body) throws IOException, GeneralSecurityException {
String subject, byte[] body) throws IOException,
GeneralSecurityException {
if((author == null) != (authorKey == null))
throw new IllegalArgumentException();
if((group == null || group.getPublicKey() == null) !=
(groupKey == null))
throw new IllegalArgumentException();
if(subject.getBytes("UTF-8").length > Message.MAX_SUBJECT_LENGTH)
throw new IllegalArgumentException();
if(body.length > Message.MAX_BODY_LENGTH)
throw new IllegalArgumentException();
@@ -98,6 +103,7 @@ class MessageEncoderImpl implements MessageEncoder {
else group.writeTo(w);
if(author == null) w.writeNull();
else author.writeTo(w);
w.writeString(subject);
long timestamp = System.currentTimeMillis();
w.writeInt64(timestamp);
byte[] salt = new byte[Message.SALT_LENGTH];
@@ -130,6 +136,7 @@ class MessageEncoderImpl implements MessageEncoder {
MessageId id = new MessageId(messageDigest.digest());
GroupId groupId = group == null ? null : group.getId();
AuthorId authorId = author == null ? null : author.getId();
return new MessageImpl(id, parent, groupId, authorId, timestamp, raw);
return new MessageImpl(id, parent, groupId, authorId, subject,
timestamp, raw);
}
}

View File

@@ -11,15 +11,17 @@ class MessageImpl implements Message {
private final MessageId id, parent;
private final GroupId group;
private final AuthorId author;
private final String subject;
private final long timestamp;
private final byte[] raw;
public MessageImpl(MessageId id, MessageId parent, GroupId group,
AuthorId author, long timestamp, byte[] raw) {
AuthorId author, String subject, long timestamp, byte[] raw) {
this.id = id;
this.parent = parent;
this.group = group;
this.author = author;
this.subject = subject;
this.timestamp = timestamp;
this.raw = raw;
}
@@ -40,6 +42,10 @@ class MessageImpl implements Message {
return author;
}
public String getSubject() {
return subject;
}
public long getTimestamp() {
return timestamp;
}

View File

@@ -76,6 +76,8 @@ class MessageReader implements ObjectReader<Message> {
author = r.readUserDefined(Types.AUTHOR, Author.class);
r.removeObjectReader(Types.AUTHOR);
}
// Read the subject
String subject = r.readString(Message.MAX_SUBJECT_LENGTH);
// Read the timestamp
long timestamp = r.readInt64();
if(timestamp < 0L) throw new FormatException();
@@ -128,6 +130,7 @@ class MessageReader implements ObjectReader<Message> {
MessageId id = new MessageId(messageDigest.digest());
GroupId groupId = group == null ? null : group.getId();
AuthorId authorId = author == null ? null : author.getId();
return new MessageImpl(id, parent, groupId, authorId, timestamp, raw);
return new MessageImpl(id, parent, groupId, authorId, subject,
timestamp, raw);
}
}

View File

@@ -75,6 +75,7 @@ public class ProtocolIntegrationTest extends TestCase {
private final Group group, group1;
private final Message message, message1, message2, message3;
private final String authorName = "Alice";
private final String subject = "Hello";
private final String messageBody = "Hello world";
private final Map<TransportId, TransportProperties> transports;
@@ -108,15 +109,17 @@ public class ProtocolIntegrationTest extends TestCase {
authorKeyPair.getPublic().getEncoded());
// Create two messages to each group: one anonymous, one pseudonymous
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
message = messageEncoder.encodeMessage(null, group,
message = messageEncoder.encodeMessage(null, group, subject,
messageBody.getBytes("UTF-8"));
message1 = messageEncoder.encodeMessage(null, group1,
groupKeyPair.getPrivate(), messageBody.getBytes("UTF-8"));
groupKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message2 = messageEncoder.encodeMessage(null, group, author,
authorKeyPair.getPrivate(), messageBody.getBytes("UTF-8"));
authorKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message3 = messageEncoder.encodeMessage(null, group1,
groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
messageBody.getBytes("UTF-8"));
subject, messageBody.getBytes("UTF-8"));
TransportProperties p =
new TransportProperties(Collections.singletonMap("bar", "baz"));
transports = Collections.singletonMap(transportId, p);

View File

@@ -52,6 +52,7 @@ public abstract class DatabaseComponentTest extends TestCase {
protected final ContactId contactId;
protected final GroupId groupId;
protected final MessageId messageId, parentId;
private final String subject;
private final long timestamp;
private final int size;
private final byte[] raw;
@@ -70,13 +71,14 @@ public abstract class DatabaseComponentTest extends TestCase {
groupId = new GroupId(TestUtils.getRandomId());
messageId = new MessageId(TestUtils.getRandomId());
parentId = new MessageId(TestUtils.getRandomId());
subject = "Foo";
timestamp = System.currentTimeMillis();
size = 1234;
raw = new byte[size];
message =
new TestMessage(messageId, null, groupId, authorId, timestamp, raw);
privateMessage =
new TestMessage(messageId, null, null, null, timestamp, raw);
message = new TestMessage(messageId, null, groupId, authorId, subject,
timestamp, raw);
privateMessage = new TestMessage(messageId, null, null, null, subject,
timestamp, raw);
group = new TestGroup(groupId, "The really exciting group", null);
transportId = new TransportId(123);
TransportProperties p =

View File

@@ -66,6 +66,7 @@ public class H2DatabaseTest extends TestCase {
private final ContactId contactId;
private final GroupId groupId;
private final MessageId messageId, privateMessageId;
private final String subject;
private final long timestamp;
private final int size;
private final byte[] raw;
@@ -91,14 +92,15 @@ public class H2DatabaseTest extends TestCase {
groupId = new GroupId(TestUtils.getRandomId());
messageId = new MessageId(TestUtils.getRandomId());
privateMessageId = new MessageId(TestUtils.getRandomId());
subject = "Foo";
timestamp = System.currentTimeMillis();
size = 1234;
raw = new byte[size];
random.nextBytes(raw);
message =
new TestMessage(messageId, null, groupId, authorId, timestamp, raw);
privateMessage =
new TestMessage(privateMessageId, null, null, null, timestamp, raw);
message = new TestMessage(messageId, null, groupId, authorId, subject,
timestamp, raw);
privateMessage = new TestMessage(privateMessageId, null, null, null,
subject, timestamp, raw);
group = groupFactory.createGroup(groupId, "Group name", null);
transportId = new TransportId(0);
properties = new TransportProperties(
@@ -797,7 +799,7 @@ public class H2DatabaseTest extends TestCase {
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new TestMessage(messageId1, null, groupId, authorId1,
timestamp, raw);
subject, timestamp, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
@@ -830,12 +832,12 @@ public class H2DatabaseTest extends TestCase {
Group group1 = groupFactory.createGroup(groupId1, "Another group name",
null);
Message child1 = new TestMessage(childId1, messageId, groupId,
authorId, timestamp, raw);
authorId, subject, timestamp, raw);
Message child2 = new TestMessage(childId2, messageId, groupId,
authorId, timestamp, raw);
authorId, subject, timestamp, raw);
// The third child is in a different group
Message child3 = new TestMessage(childId3, messageId, groupId1,
authorId, timestamp, raw);
authorId, subject, timestamp, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
@@ -866,7 +868,7 @@ public class H2DatabaseTest extends TestCase {
public void testGetOldMessages() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new TestMessage(messageId1, null, groupId, authorId,
timestamp + 1000, raw);
subject, timestamp + 1000, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
@@ -897,7 +899,7 @@ public class H2DatabaseTest extends TestCase {
byte[] largeBody = new byte[ONE_MEGABYTE];
for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
Message message1 = new TestMessage(messageId, null, groupId, authorId,
timestamp, largeBody);
subject, timestamp, largeBody);
Database<Connection> db = open(false);
// Sanity check: there should be enough space on disk for this test
@@ -1463,7 +1465,7 @@ public class H2DatabaseTest extends TestCase {
// A message with no parent should return null
MessageId childId = new MessageId(TestUtils.getRandomId());
Message child = new TestMessage(childId, null, groupId, null,
Message child = new TestMessage(childId, null, groupId, null, subject,
timestamp, raw);
db.addGroupMessage(txn, child);
assertTrue(db.containsMessage(txn, childId));
@@ -1485,7 +1487,7 @@ public class H2DatabaseTest extends TestCase {
MessageId childId = new MessageId(TestUtils.getRandomId());
MessageId parentId = new MessageId(TestUtils.getRandomId());
Message child = new TestMessage(childId, parentId, groupId, null,
timestamp, raw);
subject, timestamp, raw);
db.addGroupMessage(txn, child);
assertTrue(db.containsMessage(txn, childId));
assertFalse(db.containsMessage(txn, parentId));
@@ -1511,9 +1513,9 @@ public class H2DatabaseTest extends TestCase {
MessageId childId = new MessageId(TestUtils.getRandomId());
MessageId parentId = new MessageId(TestUtils.getRandomId());
Message child = new TestMessage(childId, parentId, groupId, null,
timestamp, raw);
subject, timestamp, raw);
Message parent = new TestMessage(parentId, null, groupId1, null,
timestamp, raw);
subject, timestamp, raw);
db.addGroupMessage(txn, child);
db.addGroupMessage(txn, parent);
assertTrue(db.containsMessage(txn, childId));
@@ -1537,7 +1539,7 @@ public class H2DatabaseTest extends TestCase {
// A message with a private parent should return null
MessageId childId = new MessageId(TestUtils.getRandomId());
Message child = new TestMessage(childId, privateMessageId, groupId,
null, timestamp, raw);
null, subject, timestamp, raw);
db.addGroupMessage(txn, child);
db.addPrivateMessage(txn, privateMessage, contactId);
assertTrue(db.containsMessage(txn, childId));
@@ -1561,9 +1563,9 @@ public class H2DatabaseTest extends TestCase {
MessageId childId = new MessageId(TestUtils.getRandomId());
MessageId parentId = new MessageId(TestUtils.getRandomId());
Message child = new TestMessage(childId, parentId, groupId, null,
timestamp, raw);
subject, timestamp, raw);
Message parent = new TestMessage(parentId, null, groupId, null,
timestamp, raw);
subject, timestamp, raw);
db.addGroupMessage(txn, child);
db.addGroupMessage(txn, parent);
assertTrue(db.containsMessage(txn, childId));

View File

@@ -10,15 +10,17 @@ class TestMessage implements Message {
private final MessageId id, parent;
private final GroupId group;
private final AuthorId author;
private final String subject;
private final long timestamp;
private final byte[] raw;
public TestMessage(MessageId id, MessageId parent, GroupId group,
AuthorId author, long timestamp, byte[] raw) {
AuthorId author, String subject, long timestamp, byte[] raw) {
this.id = id;
this.parent = parent;
this.group = group;
this.author = author;
this.subject = subject;
this.timestamp = timestamp;
this.raw = raw;
}
@@ -39,6 +41,10 @@ class TestMessage implements Message {
return author;
}
public String getSubject() {
return subject;
}
public long getTimestamp() {
return timestamp;
}

View File

@@ -46,6 +46,7 @@ public class ProtocolReadWriteTest extends TestCase {
private final BatchId batchId;
private final Group group;
private final Message message;
private final String subject = "Hello";
private final String messageBody = "Hello world";
private final BitSet bitSet;
private final Map<Group, Long> subscriptions;
@@ -64,7 +65,7 @@ public class ProtocolReadWriteTest extends TestCase {
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group", null);
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
message = messageEncoder.encodeMessage(null, group,
message = messageEncoder.encodeMessage(null, group, subject,
messageBody.getBytes("UTF-8"));
bitSet = new BitSet();
bitSet.set(3);

View File

@@ -104,9 +104,10 @@ public class ConstantsTest extends TestCase {
// Create a maximum-length message
PrivateKey groupPrivate = crypto.generateKeyPair().getPrivate();
PrivateKey authorPrivate = crypto.generateKeyPair().getPrivate();
String subject = createRandomString(Message.MAX_SUBJECT_LENGTH);
byte[] body = new byte[Message.MAX_BODY_LENGTH];
Message message = messageEncoder.encodeMessage(null, group,
groupPrivate, author, authorPrivate, body);
groupPrivate, author, authorPrivate, subject, body);
// Add the message to a batch
ByteArrayOutputStream out = new ByteArrayOutputStream(
ProtocolConstants.MAX_PACKET_LENGTH);
@@ -216,12 +217,14 @@ public class ConstantsTest extends TestCase {
assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH);
}
private static String createRandomString(int length) {
private static String createRandomString(int length) throws Exception {
StringBuilder s = new StringBuilder(length);
for(int i = 0; i < length; i++) {
int digit = (int) (Math.random() * 10);
s.append((char) ('0' + digit));
}
return s.toString();
String string = s.toString();
assertEquals(length, string.getBytes("UTF-8").length);
return string;
}
}

View File

@@ -97,9 +97,10 @@ public class BatchConnectionReadWriteTest extends TestCase {
db.open(false);
// Add Bob as a contact and send him a message
ContactId contactId = db.addContact(transports, aliceSecret);
String subject = "Hello";
byte[] messageBody = "Hi Bob!".getBytes("UTF-8");
MessageEncoder encoder = alice.getInstance(MessageEncoder.class);
Message message = encoder.encodeMessage(null, messageBody);
Message message = encoder.encodeMessage(null, subject, messageBody);
db.addLocalPrivateMessage(message, contactId);
// Create an outgoing batch connection
ByteArrayOutputStream out = new ByteArrayOutputStream();