diff --git a/api/net/sf/briar/api/protocol/Ack.java b/api/net/sf/briar/api/protocol/Ack.java index 8be9162b2..53feeb754 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 getBatchIds(); } diff --git a/api/net/sf/briar/api/protocol/Author.java b/api/net/sf/briar/api/protocol/Author.java index a3a76a375..2c72bc498 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 29e99d818..587bc7286 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 7438ecf0e..252c73caa 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 3f6a5ebac..1f57685a9 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 506aceab3..0a213a356 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 05a17ed54..31e2f4672 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 getSubscriptions(); diff --git a/api/net/sf/briar/api/protocol/TransportUpdate.java b/api/net/sf/briar/api/protocol/TransportUpdate.java index 8855fea3d..e26883c2c 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> getTransports(); diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java index 3817115cf..f6474609c 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 { r.readUserDefinedTag(Tags.ACK); r.addObjectReader(Tags.BATCH_ID, batchIdReader); Collection 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 8e430f1a5..2907689ea 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 1ee31dd15..d7fdcab4a 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 { 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 { 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 7152189ce..5dc7e9754 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 { r.readUserDefinedTag(Tags.OFFER); r.addObjectReader(Tags.MESSAGE_ID, messageIdReader); Collection 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 bf4f251c7..345acff2b 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 { List l = r.readList(TransportProperties.class); r.resetMaxStringLength(); r.removeObjectReader(Tags.TRANSPORT_PROPERTIES); + if(l.size() > TransportUpdate.MAX_PLUGINS_PER_UPDATE) + throw new FormatException(); Map> transports = new TreeMap>(); - 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 { 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 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 cb7516655..0fea45d9d 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 95863a9d0..5a9a6a2f7 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 0a310ac6b..424a021eb 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 4014710e6..aa4f64ec6 100644 --- a/test/build.xml +++ b/test/build.xml @@ -31,6 +31,7 @@ + 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 000000000..227508366 --- /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 subs = + new HashMap(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> transports = + new HashMap>( + TransportUpdate.MAX_PLUGINS_PER_UPDATE); + for(int i = 0; i < TransportUpdate.MAX_PLUGINS_PER_UPDATE; i++) { + String name = createRandomString(TransportUpdate.MAX_NAME_LENGTH); + Map properties = new HashMap( + 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(); + } +}