From 331e7e0547818a97b4dbda3e83a41eccd5720fb4 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Wed, 7 Sep 2011 13:51:30 +0100 Subject: [PATCH] Increased the maximum packet size to 1 MiB. This should provide acceptable memory usage and database locking granularity, while making subscription and transport updates large enough for the incremental update issue to be kicked into the long grass. Removed awareness of the serialisation format from the protocol component wherever possible, and added tests to ensure that the constants defined in the protocol package's API are compatible with the serialisation format. --- api/net/sf/briar/api/protocol/Ack.java | 3 + api/net/sf/briar/api/protocol/Author.java | 3 - api/net/sf/briar/api/protocol/Group.java | 3 - api/net/sf/briar/api/protocol/Message.java | 16 +- api/net/sf/briar/api/protocol/Offer.java | 3 + .../briar/api/protocol/ProtocolConstants.java | 12 +- .../api/protocol/SubscriptionUpdate.java | 3 + .../briar/api/protocol/TransportUpdate.java | 12 ++ .../net/sf/briar/protocol/AckReader.java | 2 + .../sf/briar/protocol/MessageEncoderImpl.java | 10 +- .../net/sf/briar/protocol/MessageReader.java | 6 +- .../net/sf/briar/protocol/OfferReader.java | 3 + .../sf/briar/protocol/TransportReader.java | 14 +- .../briar/protocol/writers/AckWriterImpl.java | 10 +- .../protocol/writers/BatchWriterImpl.java | 1 + .../protocol/writers/OfferWriterImpl.java | 10 +- test/build.xml | 1 + .../briar/protocol/writers/ConstantsTest.java | 188 ++++++++++++++++++ 18 files changed, 258 insertions(+), 42 deletions(-) create mode 100644 test/net/sf/briar/protocol/writers/ConstantsTest.java diff --git a/api/net/sf/briar/api/protocol/Ack.java b/api/net/sf/briar/api/protocol/Ack.java index 8be9162b23..53feeb754e 100644 --- a/api/net/sf/briar/api/protocol/Ack.java +++ b/api/net/sf/briar/api/protocol/Ack.java @@ -5,6 +5,9 @@ import java.util.Collection; /** A packet acknowledging receipt of one or more batches. */ public interface Ack { + /** The maximum number of batch IDs per ack. */ + static final int MAX_IDS_PER_ACK = 29959; + /** Returns the IDs of the acknowledged batches. */ Collection<BatchId> getBatchIds(); } diff --git a/api/net/sf/briar/api/protocol/Author.java b/api/net/sf/briar/api/protocol/Author.java index a3a76a3751..2c72bc4986 100644 --- a/api/net/sf/briar/api/protocol/Author.java +++ b/api/net/sf/briar/api/protocol/Author.java @@ -11,9 +11,6 @@ public interface Author extends Writable { /** The maximum length of an author's public key in bytes. */ static final int MAX_PUBLIC_KEY_LENGTH = 100; - /** The maximum length of a serialised author in bytes. */ - static final int MAX_LENGTH = MAX_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH + 5; - /** Returns the author's unique identifier. */ AuthorId getId(); diff --git a/api/net/sf/briar/api/protocol/Group.java b/api/net/sf/briar/api/protocol/Group.java index 29e99d818c..587bc72869 100644 --- a/api/net/sf/briar/api/protocol/Group.java +++ b/api/net/sf/briar/api/protocol/Group.java @@ -11,9 +11,6 @@ public interface Group extends Writable { /** The maximum length of a group's public key in bytes. */ static final int MAX_PUBLIC_KEY_LENGTH = 100; - /** The maximum length of a serialised group in bytes. */ - static final int MAX_LENGTH = MAX_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH + 5; - /** Returns the group's unique identifier. */ GroupId getId(); diff --git a/api/net/sf/briar/api/protocol/Message.java b/api/net/sf/briar/api/protocol/Message.java index 7438ecf0e8..252c73caa2 100644 --- a/api/net/sf/briar/api/protocol/Message.java +++ b/api/net/sf/briar/api/protocol/Message.java @@ -3,22 +3,16 @@ package net.sf.briar.api.protocol; public interface Message { /** - * The maximum length of a serialised message in bytes. To allow for future - * changes in the batch format, this is smaller than the amount of data - * that can fit in a batch using the current format. + * The maximum length of a message body in bytes. To allow for future + * changes in the protocol, this is smaller than the maximum packet length + * even when all the message's other fields have their maximum lengths. */ - static final int MAX_LENGTH = ProtocolConstants.MAX_PACKET_LENGTH - 1024; + static final int MAX_BODY_LENGTH = + ProtocolConstants.MAX_PACKET_LENGTH - 1024; /** The maximum length of a signature in bytes. */ static final int MAX_SIGNATURE_LENGTH = 100; - /** - * The maximum length of a message body in bytes. To allow for future - * changes in the message format, this is smaller than the amount of data - * that can fit in a message using the current format. - */ - static final int MAX_BODY_LENGTH = MAX_LENGTH - 1024; - /** Returns the message's unique identifier. */ MessageId getId(); diff --git a/api/net/sf/briar/api/protocol/Offer.java b/api/net/sf/briar/api/protocol/Offer.java index 3f6a5ebac9..1f57685a97 100644 --- a/api/net/sf/briar/api/protocol/Offer.java +++ b/api/net/sf/briar/api/protocol/Offer.java @@ -5,6 +5,9 @@ import java.util.Collection; /** A packet offering the recipient some messages. */ public interface Offer { + /** The maximum number of message IDs per offer. */ + static final int MAX_IDS_PER_OFFER = 29959; + /** Returns the offer's unique identifier. */ OfferId getId(); diff --git a/api/net/sf/briar/api/protocol/ProtocolConstants.java b/api/net/sf/briar/api/protocol/ProtocolConstants.java index 506aceab32..0a213a3561 100644 --- a/api/net/sf/briar/api/protocol/ProtocolConstants.java +++ b/api/net/sf/briar/api/protocol/ProtocolConstants.java @@ -1,14 +1,12 @@ package net.sf.briar.api.protocol; -import net.sf.briar.api.transport.TransportConstants; - public interface ProtocolConstants { /** - * The maximum length of a serialised packet in bytes. To allow for future - * changes in the frame format, this is smaller than the amount of data - * that can fit in a frame using the current format. + * The maximum length of a serialised packet in bytes. Since the protocol + * does not aim for low latency, the two main constraints here are the + * amount of memory used for parsing packets and the granularity of the + * database transactions for generating and receiving packets. */ - static final int MAX_PACKET_LENGTH = - TransportConstants.MAX_FRAME_LENGTH - 1024; + static final int MAX_PACKET_LENGTH = 1024 * 1024; // 1 MiB } diff --git a/api/net/sf/briar/api/protocol/SubscriptionUpdate.java b/api/net/sf/briar/api/protocol/SubscriptionUpdate.java index 05a17ed54a..31e2f4672f 100644 --- a/api/net/sf/briar/api/protocol/SubscriptionUpdate.java +++ b/api/net/sf/briar/api/protocol/SubscriptionUpdate.java @@ -5,6 +5,9 @@ import java.util.Map; /** A packet updating the sender's subscriptions. */ public interface SubscriptionUpdate { + /** The maximum number of subscriptions per update. */ + static final int MAX_SUBS_PER_UPDATE = 6000; + /** Returns the subscriptions contained in the update. */ Map<Group, Long> getSubscriptions(); diff --git a/api/net/sf/briar/api/protocol/TransportUpdate.java b/api/net/sf/briar/api/protocol/TransportUpdate.java index 8855fea3d6..e26883c2ca 100644 --- a/api/net/sf/briar/api/protocol/TransportUpdate.java +++ b/api/net/sf/briar/api/protocol/TransportUpdate.java @@ -5,6 +5,18 @@ import java.util.Map; /** A packet updating the sender's transport properties. */ public interface TransportUpdate { + /** The maximum length of a plugin's name in UTF-8 bytes. */ + static final int MAX_NAME_LENGTH = 50; + + /** The maximum length of a property's key or value in UTF-8 bytes. */ + static final int MAX_KEY_OR_VALUE_LENGTH = 100; + + /** The maximum number of properties per plugin. */ + static final int MAX_PROPERTIES_PER_PLUGIN = 100; + + /** The maximum number of plugins per update. */ + static final int MAX_PLUGINS_PER_UPDATE = 50; + /** Returns the transport properties contained in the update. */ Map<String, Map<String, String>> getTransports(); diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java index 3817115cf3..f6474609ca 100644 --- a/components/net/sf/briar/protocol/AckReader.java +++ b/components/net/sf/briar/protocol/AckReader.java @@ -3,6 +3,7 @@ package net.sf.briar.protocol; import java.io.IOException; import java.util.Collection; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.ProtocolConstants; @@ -30,6 +31,7 @@ class AckReader implements ObjectReader<Ack> { r.readUserDefinedTag(Tags.ACK); r.addObjectReader(Tags.BATCH_ID, batchIdReader); Collection<BatchId> batches = r.readList(BatchId.class); + if(batches.size() > Ack.MAX_IDS_PER_ACK) throw new FormatException(); r.removeObjectReader(Tags.BATCH_ID); r.removeConsumer(counting); // Build and return the ack diff --git a/components/net/sf/briar/protocol/MessageEncoderImpl.java b/components/net/sf/briar/protocol/MessageEncoderImpl.java index 8e430f1a57..2907689ea2 100644 --- a/components/net/sf/briar/protocol/MessageEncoderImpl.java +++ b/components/net/sf/briar/protocol/MessageEncoderImpl.java @@ -78,7 +78,10 @@ class MessageEncoderImpl implements MessageEncoder { } else { signature.initSign(authorKey); signature.update(out.toByteArray()); - w.writeBytes(signature.sign()); + byte[] sig = signature.sign(); + if(sig.length > Message.MAX_SIGNATURE_LENGTH) + throw new IllegalArgumentException(); + w.writeBytes(sig); } // Sign the message with the group's private key, if there is one if(groupKey == null) { @@ -86,7 +89,10 @@ class MessageEncoderImpl implements MessageEncoder { } else { signature.initSign(groupKey); signature.update(out.toByteArray()); - w.writeBytes(signature.sign()); + byte[] sig = signature.sign(); + if(sig.length > Message.MAX_SIGNATURE_LENGTH) + throw new IllegalArgumentException(); + w.writeBytes(sig); } // Hash the message, including the signatures, to get the message ID byte[] raw = out.toByteArray(); diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java index 1ee31dd152..d7fdcab4a8 100644 --- a/components/net/sf/briar/protocol/MessageReader.java +++ b/components/net/sf/briar/protocol/MessageReader.java @@ -14,6 +14,7 @@ import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.Group; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; +import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.serial.ObjectReader; import net.sf.briar.api.serial.Reader; @@ -41,7 +42,8 @@ class MessageReader implements ObjectReader<Message> { public Message readObject(Reader r) throws IOException { CopyingConsumer copying = new CopyingConsumer(); - CountingConsumer counting = new CountingConsumer(Message.MAX_LENGTH); + CountingConsumer counting = + new CountingConsumer(ProtocolConstants.MAX_PACKET_LENGTH); r.addConsumer(copying); r.addConsumer(counting); // Read the initial tag @@ -64,7 +66,7 @@ class MessageReader implements ObjectReader<Message> { long timestamp = r.readInt64(); if(timestamp < 0L) throw new FormatException(); // Skip the message body - r.readBytes(Message.MAX_LENGTH); + r.readBytes(Message.MAX_BODY_LENGTH); // Record the length of the data covered by the author's signature int signedByAuthor = (int) counting.getCount(); // Read the author's signature, if there is one diff --git a/components/net/sf/briar/protocol/OfferReader.java b/components/net/sf/briar/protocol/OfferReader.java index 7152189ce7..5dc7e97546 100644 --- a/components/net/sf/briar/protocol/OfferReader.java +++ b/components/net/sf/briar/protocol/OfferReader.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.security.MessageDigest; import java.util.Collection; +import net.sf.briar.api.FormatException; import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.Offer; @@ -39,6 +40,8 @@ class OfferReader implements ObjectReader<Offer> { r.readUserDefinedTag(Tags.OFFER); r.addObjectReader(Tags.MESSAGE_ID, messageIdReader); Collection<MessageId> messages = r.readList(MessageId.class); + if(messages.size() > Offer.MAX_IDS_PER_OFFER) + throw new FormatException(); r.removeObjectReader(Tags.MESSAGE_ID); r.removeConsumer(digesting); r.removeConsumer(counting); diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java index bf4f251c70..345acff2b0 100644 --- a/components/net/sf/briar/protocol/TransportReader.java +++ b/components/net/sf/briar/protocol/TransportReader.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import net.sf.briar.api.FormatException; import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.TransportUpdate; @@ -34,9 +35,14 @@ class TransportReader implements ObjectReader<TransportUpdate> { List<TransportProperties> l = r.readList(TransportProperties.class); r.resetMaxStringLength(); r.removeObjectReader(Tags.TRANSPORT_PROPERTIES); + if(l.size() > TransportUpdate.MAX_PLUGINS_PER_UPDATE) + throw new FormatException(); Map<String, Map<String, String>> transports = new TreeMap<String, Map<String, String>>(); - for(TransportProperties t : l) transports.put(t.name, t.properties); + for(TransportProperties t : l) { + if(transports.put(t.name, t.properties) != null) + throw new FormatException(); // Duplicate plugin name + } long timestamp = r.readInt64(); r.removeConsumer(counting); // Build and return the transport update @@ -59,9 +65,13 @@ class TransportReader implements ObjectReader<TransportUpdate> { public TransportProperties readObject(Reader r) throws IOException { r.readUserDefinedTag(Tags.TRANSPORT_PROPERTIES); - String name = r.readString(); + String name = r.readString(TransportUpdate.MAX_NAME_LENGTH); + r.setMaxStringLength(TransportUpdate.MAX_KEY_OR_VALUE_LENGTH); Map<String, String> properties = r.readMap(String.class, String.class); + r.resetMaxStringLength(); + if(properties.size() > TransportUpdate.MAX_PROPERTIES_PER_PLUGIN) + throw new FormatException(); return new TransportProperties(name, properties); } } diff --git a/components/net/sf/briar/protocol/writers/AckWriterImpl.java b/components/net/sf/briar/protocol/writers/AckWriterImpl.java index cb7516655e..0fea45d9da 100644 --- a/components/net/sf/briar/protocol/writers/AckWriterImpl.java +++ b/components/net/sf/briar/protocol/writers/AckWriterImpl.java @@ -3,8 +3,8 @@ package net.sf.briar.protocol.writers; import java.io.IOException; import java.io.OutputStream; +import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.BatchId; -import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.writers.AckWriter; import net.sf.briar.api.serial.Writer; @@ -16,6 +16,7 @@ class AckWriterImpl implements AckWriter { private final Writer w; private boolean started = false; + private int idsWritten = 0; AckWriterImpl(OutputStream out, WriterFactory writerFactory) { this.out = out; @@ -28,12 +29,9 @@ class AckWriterImpl implements AckWriter { w.writeListStart(); started = true; } - int capacity = ProtocolConstants.MAX_PACKET_LENGTH - - (int) w.getBytesWritten() - 1; - // Allow one byte for the BATCH_ID tag, one byte for the BYTES tag and - // one byte for the length as a uint7 - if(capacity < BatchId.LENGTH + 3) return false; + if(idsWritten >= Ack.MAX_IDS_PER_ACK) return false; b.writeTo(w); + idsWritten++; return true; } diff --git a/components/net/sf/briar/protocol/writers/BatchWriterImpl.java b/components/net/sf/briar/protocol/writers/BatchWriterImpl.java index 95863a9d07..5a9a6a2f74 100644 --- a/components/net/sf/briar/protocol/writers/BatchWriterImpl.java +++ b/components/net/sf/briar/protocol/writers/BatchWriterImpl.java @@ -40,6 +40,7 @@ class BatchWriterImpl implements BatchWriter { w.writeListStart(); started = true; } + // Allow one byte for the list end tag int capacity = ProtocolConstants.MAX_PACKET_LENGTH - (int) w.getBytesWritten() - 1; if(capacity < message.length) return false; diff --git a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java index 0a310ac6b3..424a021ebd 100644 --- a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java +++ b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java @@ -6,8 +6,8 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import net.sf.briar.api.protocol.MessageId; +import net.sf.briar.api.protocol.Offer; import net.sf.briar.api.protocol.OfferId; -import net.sf.briar.api.protocol.ProtocolConstants; import net.sf.briar.api.protocol.Tags; import net.sf.briar.api.protocol.writers.OfferWriter; import net.sf.briar.api.serial.Writer; @@ -20,6 +20,7 @@ class OfferWriterImpl implements OfferWriter { private final MessageDigest messageDigest; private boolean started = false; + private int idsWritten = 0; OfferWriterImpl(OutputStream out, WriterFactory writerFactory, MessageDigest messageDigest) { @@ -35,12 +36,9 @@ class OfferWriterImpl implements OfferWriter { w.writeListStart(); started = true; } - int capacity = ProtocolConstants.MAX_PACKET_LENGTH - - (int) w.getBytesWritten() - 1; - // Allow one byte for the MESSAGE_ID tag, one byte for the BYTES tag - // and one byte for the length as a uint7 - if(capacity < MessageId.LENGTH + 3) return false; + if(idsWritten >= Offer.MAX_IDS_PER_OFFER) return false; m.writeTo(w); + idsWritten++; return true; } diff --git a/test/build.xml b/test/build.xml index 4014710e65..aa4f64ec67 100644 --- a/test/build.xml +++ b/test/build.xml @@ -31,6 +31,7 @@ <test name='net.sf.briar.protocol.ConsumersTest'/> <test name='net.sf.briar.protocol.ProtocolReadWriteTest'/> <test name='net.sf.briar.protocol.RequestReaderTest'/> + <test name='net.sf.briar.protocol.writers.ConstantsTest'/> <test name='net.sf.briar.protocol.writers.RequestWriterImplTest'/> <test name='net.sf.briar.serial.ReaderImplTest'/> <test name='net.sf.briar.serial.WriterImplTest'/> diff --git a/test/net/sf/briar/protocol/writers/ConstantsTest.java b/test/net/sf/briar/protocol/writers/ConstantsTest.java new file mode 100644 index 0000000000..2275083668 --- /dev/null +++ b/test/net/sf/briar/protocol/writers/ConstantsTest.java @@ -0,0 +1,188 @@ +package net.sf.briar.protocol.writers; + +import java.io.ByteArrayOutputStream; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; +import net.sf.briar.TestUtils; +import net.sf.briar.api.crypto.CryptoComponent; +import net.sf.briar.api.protocol.Ack; +import net.sf.briar.api.protocol.Author; +import net.sf.briar.api.protocol.AuthorFactory; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.Group; +import net.sf.briar.api.protocol.GroupFactory; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.protocol.MessageEncoder; +import net.sf.briar.api.protocol.MessageId; +import net.sf.briar.api.protocol.Offer; +import net.sf.briar.api.protocol.ProtocolConstants; +import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportUpdate; +import net.sf.briar.api.protocol.UniqueId; +import net.sf.briar.api.protocol.writers.AckWriter; +import net.sf.briar.api.protocol.writers.BatchWriter; +import net.sf.briar.api.protocol.writers.OfferWriter; +import net.sf.briar.api.protocol.writers.SubscriptionWriter; +import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.serial.WriterFactory; +import net.sf.briar.crypto.CryptoModule; +import net.sf.briar.protocol.ProtocolModule; +import net.sf.briar.serial.SerialModule; + +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +public class ConstantsTest extends TestCase { + + private final WriterFactory writerFactory; + private final CryptoComponent crypto; + private final GroupFactory groupFactory; + private final AuthorFactory authorFactory; + private final MessageEncoder messageEncoder; + + public ConstantsTest() throws Exception { + super(); + Injector i = Guice.createInjector(new CryptoModule(), + new ProtocolModule(), new SerialModule()); + writerFactory = i.getInstance(WriterFactory.class); + crypto = i.getInstance(CryptoComponent.class); + groupFactory = i.getInstance(GroupFactory.class); + authorFactory = i.getInstance(AuthorFactory.class); + messageEncoder = i.getInstance(MessageEncoder.class); + } + + @Test + public void testBatchesFitIntoAck() throws Exception { + // Create an ack with the maximum number of batch IDs + ByteArrayOutputStream out = new ByteArrayOutputStream( + ProtocolConstants.MAX_PACKET_LENGTH); + AckWriter a = new AckWriterImpl(out, writerFactory); + for(int i = 0; i < Ack.MAX_IDS_PER_ACK; i++) { + assertTrue(a.writeBatchId(new BatchId(TestUtils.getRandomId()))); + } + // Check that no more batch IDs can be written + assertFalse(a.writeBatchId(new BatchId(TestUtils.getRandomId()))); + a.finish(); + // Check the size of the ack + assertTrue(out.size() > UniqueId.LENGTH * Ack.MAX_IDS_PER_ACK); + assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH); + } + + @Test + public void testMessageFitsIntoBatch() throws Exception { + // Create a maximum-length group + String groupName = createRandomString(Group.MAX_NAME_LENGTH); + byte[] groupPublic = new byte[Group.MAX_PUBLIC_KEY_LENGTH]; + Group group = groupFactory.createGroup(groupName, groupPublic); + // Create a maximum-length author + String authorName = createRandomString(Author.MAX_NAME_LENGTH); + byte[] authorPublic = new byte[Author.MAX_PUBLIC_KEY_LENGTH]; + Author author = authorFactory.createAuthor(authorName, authorPublic); + // Create a maximum-length message + PrivateKey groupPrivate = crypto.generateKeyPair().getPrivate(); + PrivateKey authorPrivate = crypto.generateKeyPair().getPrivate(); + byte[] body = new byte[Message.MAX_BODY_LENGTH]; + Message message = messageEncoder.encodeMessage(MessageId.NONE, group, + groupPrivate, author, authorPrivate, body); + // Add the message to a batch + ByteArrayOutputStream out = new ByteArrayOutputStream( + ProtocolConstants.MAX_PACKET_LENGTH); + BatchWriter b = new BatchWriterImpl(out, writerFactory, + crypto.getMessageDigest()); + b.writeMessage(message.getBytes()); + b.finish(); + // Check the size of the serialised batch + assertTrue(out.size() > UniqueId.LENGTH + Group.MAX_NAME_LENGTH + + Group.MAX_PUBLIC_KEY_LENGTH + Author.MAX_NAME_LENGTH + + Author.MAX_PUBLIC_KEY_LENGTH + Message.MAX_BODY_LENGTH); + assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH); + } + + @Test + public void testMessagesFitIntoOffer() throws Exception { + // Create an offer with the maximum number of message IDs + ByteArrayOutputStream out = new ByteArrayOutputStream( + ProtocolConstants.MAX_PACKET_LENGTH); + OfferWriter o = new OfferWriterImpl(out, writerFactory, + crypto.getMessageDigest()); + for(int i = 0; i < Offer.MAX_IDS_PER_OFFER; i++) { + assertTrue(o.writeMessageId(new MessageId( + TestUtils.getRandomId()))); + } + // Check that no more message IDs can be written + assertFalse(o.writeMessageId(new MessageId(TestUtils.getRandomId()))); + o.finish(); + // Check the size of the offer + assertTrue(out.size() > UniqueId.LENGTH * Offer.MAX_IDS_PER_OFFER); + assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH); + } + + @Test + public void testSubscriptionsFitIntoUpdate() throws Exception { + // Create the maximum number of maximum-length subscriptions + Map<Group, Long> subs = + new HashMap<Group, Long>(SubscriptionUpdate.MAX_SUBS_PER_UPDATE); + byte[] publicKey = new byte[Group.MAX_PUBLIC_KEY_LENGTH]; + for(int i = 0; i < SubscriptionUpdate.MAX_SUBS_PER_UPDATE; i++) { + String name = createRandomString(Group.MAX_NAME_LENGTH); + Group group = groupFactory.createGroup(name, publicKey); + subs.put(group, Long.MAX_VALUE); + } + // Add the subscriptions to an update + ByteArrayOutputStream out = new ByteArrayOutputStream( + ProtocolConstants.MAX_PACKET_LENGTH); + SubscriptionWriter s = new SubscriptionWriterImpl(out, writerFactory); + s.writeSubscriptions(subs, Long.MAX_VALUE); + // Check the size of the serialised update + assertTrue(out.size() > SubscriptionUpdate.MAX_SUBS_PER_UPDATE * + (Group.MAX_NAME_LENGTH + Group.MAX_PUBLIC_KEY_LENGTH + 8) + 8); + assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH); + } + + @Test + public void testTransportsFitIntoUpdate() throws Exception { + // Create the maximum number of plugins, each with the maximum number + // of maximum-length properties + Map<String, Map<String, String>> transports = + new HashMap<String, Map<String, String>>( + TransportUpdate.MAX_PLUGINS_PER_UPDATE); + for(int i = 0; i < TransportUpdate.MAX_PLUGINS_PER_UPDATE; i++) { + String name = createRandomString(TransportUpdate.MAX_NAME_LENGTH); + Map<String, String> properties = new HashMap<String, String>( + TransportUpdate.MAX_PROPERTIES_PER_PLUGIN); + for(int j = 0; j < TransportUpdate.MAX_PROPERTIES_PER_PLUGIN; j++) { + String key = createRandomString( + TransportUpdate.MAX_KEY_OR_VALUE_LENGTH); + String value = createRandomString( + TransportUpdate.MAX_KEY_OR_VALUE_LENGTH); + properties.put(key, value); + } + transports.put(name, properties); + } + // Add the transports to an update + ByteArrayOutputStream out = new ByteArrayOutputStream( + ProtocolConstants.MAX_PACKET_LENGTH); + TransportWriter t = new TransportWriterImpl(out, writerFactory); + t.writeTransports(transports, Long.MAX_VALUE); + // Check the size of the serialised update + assertTrue(out.size() > TransportUpdate.MAX_PLUGINS_PER_UPDATE * + (TransportUpdate.MAX_NAME_LENGTH + + TransportUpdate.MAX_PROPERTIES_PER_PLUGIN * + TransportUpdate.MAX_KEY_OR_VALUE_LENGTH * 2)); + assertTrue(out.size() <= ProtocolConstants.MAX_PACKET_LENGTH); + } + + private static String createRandomString(int length) { + 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(); + } +} -- GitLab