diff --git a/briar-api/src/net/sf/briar/api/db/AckAndRequest.java b/briar-api/src/net/sf/briar/api/db/AckAndRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..11959a17d90a0c4a7762b757b819109584d02f66 --- /dev/null +++ b/briar-api/src/net/sf/briar/api/db/AckAndRequest.java @@ -0,0 +1,27 @@ +package net.sf.briar.api.db; + +import net.sf.briar.api.messaging.Ack; +import net.sf.briar.api.messaging.Request; + +/** + * A tuple of an {@link net.sf.briar.api.messaging.Ack} and a + * {@link net.sf.briar.api.messaging.Request}. + */ +public class AckAndRequest { + + private final Ack ack; + private final Request request; + + public AckAndRequest(Ack ack, Request request) { + this.ack = ack; + this.request = request; + } + + public Ack getAck() { + return ack; + } + + public Request getRequest() { + return request; + } +} diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java index 5dcf0898621f9d76f678b15bb945d68d591c978b..312dfd18ecaf4606d6b56d20155270356c3121d6 100644 --- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java +++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java @@ -20,7 +20,6 @@ import net.sf.briar.api.messaging.GroupStatus; import net.sf.briar.api.messaging.Message; import net.sf.briar.api.messaging.MessageId; import net.sf.briar.api.messaging.Offer; -import net.sf.briar.api.messaging.Request; import net.sf.briar.api.messaging.RetentionAck; import net.sf.briar.api.messaging.RetentionUpdate; import net.sf.briar.api.messaging.SubscriptionAck; @@ -98,9 +97,9 @@ public interface DatabaseComponent { * collection of requested messages, with a total length less than or equal * to the given length, for transmission over a transport with the given * maximum latency. Any messages that were either added to the batch, or - * were considered but are no longer sendable to the contact, are removed - * from the collection of requested messages before returning. Returns null - * if there are no sendable messages that fit in the given length. + * were considered but are not sendable to the contact, are removed from + * the collection of requested messages before returning. Returns null if + * there are no sendable messages that fit in the given length. */ Collection<byte[]> generateBatch(ContactId c, int maxLength, long maxLatency, Collection<MessageId> requested) @@ -259,14 +258,17 @@ public interface DatabaseComponent { void receiveMessage(ContactId c, Message m) throws DbException; /** - * Processes an offer from the given contact and generates a request for - * any messages in the offer that the contact should send. To prevent - * contacts from using offers to test for subscriptions that are not - * visible to them, any messages belonging to groups that are not visible - * to the contact are requested just as though they were not present in the - * database. + * Processes an offer from the given contact and generates an ack for any + * messages in the offer that are present in the database, and a request + * for any messages that are not. The ack or the request may be null if no + * messages meet the respective criteria. + * <p> + * To prevent contacts from using offers to test for subscriptions that are + * not visible to them, any messages belonging to groups that are not + * visible to the contact are requested just as though they were not + * present in the database. */ - Request receiveOffer(ContactId c, Offer o) throws DbException; + AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException; /** Processes a retention ack from the given contact. */ void receiveRetentionAck(ContactId c, RetentionAck a) throws DbException; diff --git a/briar-api/src/net/sf/briar/api/messaging/Request.java b/briar-api/src/net/sf/briar/api/messaging/Request.java index e789b4f353290829086f1cdaf946d46b3afa7579..ec22cb8e5fa98fbf9797e8d44153534890b3d984 100644 --- a/briar-api/src/net/sf/briar/api/messaging/Request.java +++ b/briar-api/src/net/sf/briar/api/messaging/Request.java @@ -1,31 +1,18 @@ package net.sf.briar.api.messaging; -import java.util.BitSet; +import java.util.Collection; -/** - * A packet requesting some or all of the {@link Message}s from an - * {@link Offer}. - */ +/** A packet requesting one or more {@link Message}s from the recipient. */ public class Request { - private final BitSet requested; - private final int length; + private final Collection<MessageId> requested; - public Request(BitSet requested, int length) { + public Request(Collection<MessageId> requested) { this.requested = requested; - this.length = length; } - /** - * Returns a sequence of bits corresponding to the sequence of messages in - * the offer, where the i^th bit is set if the i^th message should be sent. - */ - public BitSet getBitmap() { + /** Returns the identifiers of the requested messages. */ + public Collection<MessageId> getMessageIds() { return requested; } - - /** Returns the length of the bitmap in bits. */ - public int getLength() { - return length; - } } diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index 7c43a3a48e283b82c23e7aad223cdd6c5813895a..75778cc59555ebc276f308d5a727ba61220dbcf1 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -11,7 +11,6 @@ import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE; import java.io.IOException; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -32,6 +31,7 @@ import net.sf.briar.api.TransportConfig; import net.sf.briar.api.TransportId; import net.sf.briar.api.TransportProperties; import net.sf.briar.api.clock.Clock; +import net.sf.briar.api.db.AckAndRequest; import net.sf.briar.api.db.ContactExistsException; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; @@ -1402,9 +1402,9 @@ DatabaseCleaner.Callback { return storeGroupMessage(txn, m, c); } - public Request receiveOffer(ContactId c, Offer o) throws DbException { - Collection<MessageId> offered; - BitSet request; + public AckAndRequest receiveOffer(ContactId c, Offer o) throws DbException { + List<MessageId> ack = new ArrayList<MessageId>(); + List<MessageId> request = new ArrayList<MessageId>(); contactLock.readLock().lock(); try { messageLock.writeLock().lock(); @@ -1415,15 +1415,10 @@ DatabaseCleaner.Callback { try { if(!db.containsContact(txn, c)) throw new NoSuchContactException(); - offered = o.getMessageIds(); - request = new BitSet(offered.size()); - Iterator<MessageId> it = offered.iterator(); - for(int i = 0; it.hasNext(); i++) { - // If the message is not in the database, or not - // visible to the contact, request it - MessageId m = it.next(); - if(!db.setStatusSeenIfVisible(txn, c, m)) - request.set(i); + for(MessageId m : o.getMessageIds()) { + // If the message is present and visible, ack it + if(db.setStatusSeenIfVisible(txn, c, m)) ack.add(m); + else request.add(m); } db.commitTransaction(txn); } catch(DbException e) { @@ -1439,7 +1434,9 @@ DatabaseCleaner.Callback { } finally { contactLock.readLock().unlock(); } - return new Request(request, offered.size()); + Ack a = ack.isEmpty() ? null : new Ack(ack); + Request r = request.isEmpty() ? null : new Request(request); + return new AckAndRequest(a, r); } public void receiveRetentionAck(ContactId c, RetentionAck a) diff --git a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java index ff30595e40120b9aafb29c592c993792befa1bd1..bb579237138d2a187963c8846af0b256d9ffe4e5 100644 --- a/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java +++ b/briar-core/src/net/sf/briar/messaging/PacketReaderImpl.java @@ -17,7 +17,6 @@ import static net.sf.briar.api.messaging.Types.TRANSPORT_UPDATE; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -113,22 +112,22 @@ class PacketReaderImpl implements PacketReader { // Read the start of the struct r.readStructStart(OFFER); // Read the message IDs - List<MessageId> messages = new ArrayList<MessageId>(); + List<MessageId> offered = new ArrayList<MessageId>(); r.readListStart(); while(!r.hasListEnd()) { byte[] b = r.readBytes(UniqueId.LENGTH); if(b.length != UniqueId.LENGTH) throw new FormatException(); - messages.add(new MessageId(b)); + offered.add(new MessageId(b)); } - if(messages.isEmpty()) throw new FormatException(); + if(offered.isEmpty()) throw new FormatException(); r.readListEnd(); // Read the end of the struct r.readStructEnd(); // Reset the reader r.removeConsumer(counting); // Build and return the offer - return new Offer(Collections.unmodifiableList(messages)); + return new Offer(Collections.unmodifiableList(offered)); } public boolean hasRequest() throws IOException { @@ -141,25 +140,23 @@ class PacketReaderImpl implements PacketReader { r.addConsumer(counting); // Read the start of the struct r.readStructStart(REQUEST); - // There may be up to 7 bits of padding at the end of the bitmap - int padding = r.readUint7(); - if(padding > 7) throw new FormatException(); - // Read the bitmap - byte[] bitmap = r.readBytes(MAX_PACKET_LENGTH); + // Read the message IDs + List<MessageId> requested = new ArrayList<MessageId>(); + r.readListStart(); + while(!r.hasListEnd()) { + byte[] b = r.readBytes(UniqueId.LENGTH); + if(b.length != UniqueId.LENGTH) + throw new FormatException(); + requested.add(new MessageId(b)); + } + if(requested.isEmpty()) throw new FormatException(); + r.readListEnd(); // Read the end of the struct r.readStructEnd(); // Reset the reader r.removeConsumer(counting); - // Convert the bitmap into a BitSet - int length = bitmap.length * 8 - padding; - BitSet b = new BitSet(length); - for(int i = 0; i < bitmap.length; i++) { - for(int j = 0; j < 8 && i * 8 + j < length; j++) { - byte bit = (byte) (128 >> j); - if((bitmap[i] & bit) != 0) b.set(i * 8 + j); - } - } - return new Request(b, length); + // Build and return the request + return new Request(Collections.unmodifiableList(requested)); } public boolean hasRetentionAck() throws IOException { diff --git a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java index 9b2607f8dd5b092c0e141db750120dce4f439e09..281d5a36b927fdf6daeaf976bea75b6c42fe3b0b 100644 --- a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java +++ b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java @@ -14,7 +14,6 @@ import static net.sf.briar.api.messaging.Types.TRANSPORT_UPDATE; import java.io.IOException; import java.io.OutputStream; -import java.util.BitSet; import net.sf.briar.api.messaging.Ack; import net.sf.briar.api.messaging.Group; @@ -92,22 +91,10 @@ class PacketWriterImpl implements PacketWriter { } public void writeRequest(Request r) throws IOException { - BitSet b = r.getBitmap(); - int length = r.getLength(); - // If the number of bits isn't a multiple of 8, round up to a byte - int bytes = length % 8 == 0 ? length / 8 : length / 8 + 1; - byte[] bitmap = new byte[bytes]; - // I'm kind of surprised BitSet doesn't have a method for this - for(int i = 0; i < length; i++) { - if(b.get(i)) { - int offset = i / 8; - byte bit = (byte) (128 >> i % 8); - bitmap[offset] |= bit; - } - } w.writeStructStart(REQUEST); - w.writeUint7((byte) (bytes * 8 - length)); - w.writeBytes(bitmap); + w.writeListStart(); + for(MessageId m : r.getMessageIds()) w.writeBytes(m.getBytes()); + w.writeListEnd(); w.writeStructEnd(); if(flush) out.flush(); } diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java index d671b0907242525ded1107165d36b8c595eca2e2..97b7ad41588408d7cfe5f27739ddc3b450ac75eb 100644 --- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java +++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java @@ -8,12 +8,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.BitSet; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -23,6 +18,7 @@ import java.util.logging.Logger; import net.sf.briar.api.ContactId; import net.sf.briar.api.FormatException; import net.sf.briar.api.TransportId; +import net.sf.briar.api.db.AckAndRequest; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.event.ContactRemovedEvent; @@ -93,8 +89,6 @@ abstract class DuplexConnection implements DatabaseListener { private final AtomicBoolean canSendOffer, disposed; private final BlockingQueue<Runnable> writerTasks; - private Collection<MessageId> offered = null; // Locking: this - private volatile PacketWriter writer = null; DuplexConnection(Executor dbExecutor, Executor cryptoExecutor, @@ -142,8 +136,11 @@ abstract class DuplexConnection implements DatabaseListener { } else if(e instanceof LocalSubscriptionsUpdatedEvent) { LocalSubscriptionsUpdatedEvent l = (LocalSubscriptionsUpdatedEvent) e; - if(l.getAffectedContacts().contains(contactId)) + if(l.getAffectedContacts().contains(contactId)) { dbExecutor.execute(new GenerateSubscriptionUpdate()); + if(canSendOffer.getAndSet(false)) + dbExecutor.execute(new GenerateOffer()); + } } else if(e instanceof LocalTransportsUpdatedEvent) { dbExecutor.execute(new GenerateTransportUpdates()); } else if(e instanceof MessageReceivedEvent) { @@ -159,6 +156,8 @@ abstract class DuplexConnection implements DatabaseListener { dbExecutor.execute(new GenerateRetentionAck()); } else if(e instanceof RemoteSubscriptionsUpdatedEvent) { dbExecutor.execute(new GenerateSubscriptionAck()); + if(canSendOffer.getAndSet(false)) + dbExecutor.execute(new GenerateOffer()); } else if(e instanceof RemoteTransportsUpdatedEvent) { dbExecutor.execute(new GenerateTransportAcks()); } @@ -185,24 +184,7 @@ abstract class DuplexConnection implements DatabaseListener { } else if(reader.hasRequest()) { Request r = reader.readRequest(); if(LOG.isLoggable(INFO)) LOG.info("Received request"); - // Retrieve the offered message IDs - Collection<MessageId> offered = getOfferedMessageIds(); - if(offered == null) throw new FormatException(); - // Work out which messages were requested - BitSet b = r.getBitmap(); - List<MessageId> requested = new LinkedList<MessageId>(); - List<MessageId> seen = new ArrayList<MessageId>(); - int i = 0; - for(MessageId m : offered) { - if(b.get(i++)) requested.add(m); - else seen.add(m); - } - requested = Collections.synchronizedList(requested); - seen = Collections.unmodifiableList(seen); - // Mark the unrequested messages as seen - dbExecutor.execute(new SetSeen(seen)); - // Start sending the requested messages - dbExecutor.execute(new GenerateBatches(requested)); + dbExecutor.execute(new GenerateBatches(r.getMessageIds())); } else if(reader.hasRetentionAck()) { RetentionAck a = reader.readRetentionAck(); if(LOG.isLoggable(INFO)) LOG.info("Received retention ack"); @@ -244,17 +226,6 @@ abstract class DuplexConnection implements DatabaseListener { } } - private synchronized Collection<MessageId> getOfferedMessageIds() { - Collection<MessageId> ids = offered; - offered = null; - return ids; - } - - private synchronized void setOfferedMessageIds(Collection<MessageId> ids) { - assert offered == null; - offered = ids; - } - void write() { connRegistry.registerConnection(contactId, transportId); db.addListener(this); @@ -383,9 +354,15 @@ abstract class DuplexConnection implements DatabaseListener { public void run() { try { - Request r = db.receiveOffer(contactId, offer); - if(LOG.isLoggable(INFO)) LOG.info("DB received offer"); - writerTasks.add(new WriteRequest(r)); + AckAndRequest ar = db.receiveOffer(contactId, offer); + Ack a = ar.getAck(); + Request r = ar.getRequest(); + if(LOG.isLoggable(INFO)) { + LOG.info("DB received offer: " + (a != null) + + " " + (r != null)); + } + if(a != null) writerTasks.add(new WriteAck(a)); + if(r != null) writerTasks.add(new WriteRequest(r)); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } @@ -413,25 +390,6 @@ abstract class DuplexConnection implements DatabaseListener { } } - // This task runs on a database thread - private class SetSeen implements Runnable { - - private final Collection<MessageId> seen; - - private SetSeen(Collection<MessageId> seen) { - this.seen = seen; - } - - public void run() { - try { - db.setSeen(contactId, seen); - if(LOG.isLoggable(INFO)) LOG.info("DB set seen"); - } catch(DbException e) { - if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - } - // This task runs on a database thread private class ReceiveRetentionAck implements Runnable { @@ -649,15 +607,8 @@ abstract class DuplexConnection implements DatabaseListener { Offer o = db.generateOffer(contactId, maxMessages); if(LOG.isLoggable(INFO)) LOG.info("Generated offer: " + (o != null)); - if(o == null) { - // No messages to offer - wait for some to be added - canSendOffer.set(true); - } else { - // Store the offered message IDs - setOfferedMessageIds(o.getMessageIds()); - // Write the offer on the writer thread - writerTasks.add(new WriteOffer(o)); - } + if(o == null) canSendOffer.set(true); + else writerTasks.add(new WriteOffer(o)); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } diff --git a/briar-tests/build.xml b/briar-tests/build.xml index c316b4c8d0b07e75900ed7b1646f7969e0882e78..40ca67c1b552ba2cebc9a3a993cd23c7c861015c 100644 --- a/briar-tests/build.xml +++ b/briar-tests/build.xml @@ -107,7 +107,6 @@ <test name='net.sf.briar.messaging.ConstantsTest'/> <test name='net.sf.briar.messaging.ConsumersTest'/> <test name='net.sf.briar.messaging.PacketReaderImplTest'/> - <test name='net.sf.briar.messaging.PacketWriterImplTest'/> <test name='net.sf.briar.messaging.simplex.OutgoingSimplexConnectionTest'/> <test name='net.sf.briar.messaging.simplex.SimplexMessagingIntegrationTest'/> <test name='net.sf.briar.plugins.PluginManagerImplTest'/> diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java index ad98a62b1847c8d50baefd8f8b6f8a68882d1ff2..2d4cbcb9503765fc9c8e62376951369cb40c8a8e 100644 --- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java +++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java @@ -9,7 +9,6 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.Random; @@ -138,9 +137,7 @@ public class ProtocolIntegrationTest extends BriarTestCase { writer.writeOffer(new Offer(messageIds)); - BitSet requested = new BitSet(2); - requested.set(1); - writer.writeRequest(new Request(requested, 2)); + writer.writeRequest(new Request(messageIds)); SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1); writer.writeSubscriptionUpdate(su); @@ -187,11 +184,7 @@ public class ProtocolIntegrationTest extends BriarTestCase { // Read the request assertTrue(reader.hasRequest()); Request req = reader.readRequest(); - BitSet requested = req.getBitmap(); - assertFalse(requested.get(0)); - assertTrue(requested.get(1)); - // If there are any padding bits, they should all be zero - assertEquals(1, requested.cardinality()); + assertEquals(messageIds, req.getMessageIds()); // Read the subscription update assertTrue(reader.hasSubscriptionUpdate()); diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index 34b9c6e6f164fff47c4041e47506cbc00f3207ec..a35816527eff1cfe379c9f40d6d28f1d7fdd754f 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -22,6 +22,7 @@ import net.sf.briar.api.LocalAuthor; import net.sf.briar.api.TransportConfig; import net.sf.briar.api.TransportId; import net.sf.briar.api.TransportProperties; +import net.sf.briar.api.db.AckAndRequest; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.NoSuchSubscriptionException; @@ -1077,7 +1078,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId); will(returnValue(false)); // Not visible - request message # 0 oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId1); - will(returnValue(true)); // Visible - do not request message # 1 + will(returnValue(true)); // Visible - ack message # 1 oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2); will(returnValue(false)); // Not visible - request message # 2 }}); @@ -1085,9 +1086,13 @@ public abstract class DatabaseComponentTest extends BriarTestCase { shutdown); Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2)); - Request r = db.receiveOffer(contactId, o); - assertEquals(expectedRequest, r.getBitmap()); - assertEquals(3, r.getLength()); + AckAndRequest ar = db.receiveOffer(contactId, o); + Ack a = ar.getAck(); + assertNotNull(a); + assertEquals(Arrays.asList(messageId1), a.getMessageIds()); + Request r = ar.getRequest(); + assertNotNull(r); + assertEquals(Arrays.asList(messageId, messageId2), r.getMessageIds()); context.assertIsSatisfied(); } diff --git a/briar-tests/src/net/sf/briar/messaging/PacketReaderImplTest.java b/briar-tests/src/net/sf/briar/messaging/PacketReaderImplTest.java index 43a2f4427c3a42cddb3d576bb394193ea6e6f4e7..982391208ee579d33d3775541355f26806f6a9ee 100644 --- a/briar-tests/src/net/sf/briar/messaging/PacketReaderImplTest.java +++ b/briar-tests/src/net/sf/briar/messaging/PacketReaderImplTest.java @@ -7,12 +7,10 @@ import static net.sf.briar.api.messaging.Types.REQUEST; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.BitSet; import net.sf.briar.BriarTestCase; import net.sf.briar.TestUtils; import net.sf.briar.api.FormatException; -import net.sf.briar.api.messaging.Request; import net.sf.briar.api.serial.ReaderFactory; import net.sf.briar.api.serial.SerialComponent; import net.sf.briar.api.serial.Writer; @@ -127,39 +125,15 @@ public class PacketReaderImplTest extends BriarTestCase { } @Test - public void testBitmapDecoding() throws Exception { - // Test sizes from 0 to 1000 bits - for(int i = 0; i < 1000; i++) { - // Create a BitSet of size i with one in ten bits set (on average) - BitSet requested = new BitSet(i); - for(int j = 0; j < i; j++) if(Math.random() < 0.1) requested.set(j); - // Encode the BitSet as a bitmap - int bytes = i % 8 == 0 ? i / 8 : i / 8 + 1; - byte[] bitmap = new byte[bytes]; - for(int j = 0; j < i; j++) { - if(requested.get(j)) { - int offset = j / 8; - byte bit = (byte) (128 >> j % 8); - bitmap[offset] |= bit; - } - } - // Create a serialised request containing the bitmap - byte[] b = createRequest(bitmap); - // Deserialise the request - ByteArrayInputStream in = new ByteArrayInputStream(b); - PacketReaderImpl reader = new PacketReaderImpl(readerFactory, - null, null, in); - Request request = reader.readRequest(); - BitSet decoded = request.getBitmap(); - // Check that the decoded BitSet matches the original - we can't - // use equals() because of padding, but the first i bits should - // match and the cardinalities should be equal, indicating that no - // padding bits are set - for(int j = 0; j < i; j++) { - assertEquals(requested.get(j), decoded.get(j)); - } - assertEquals(requested.cardinality(), decoded.cardinality()); - } + public void testEmptyRequest() throws Exception { + byte[] b = createEmptyRequest(); + ByteArrayInputStream in = new ByteArrayInputStream(b); + PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null, + null, in); + try { + reader.readRequest(); + fail(); + } catch(FormatException expected) {} } private byte[] createAck(boolean tooBig) throws Exception { @@ -222,26 +196,26 @@ public class PacketReaderImplTest extends BriarTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); Writer w = writerFactory.createWriter(out); w.writeStructStart(REQUEST); - // Allow one byte for the STRUCT tag, one byte for the struct ID, - // one byte for the padding length as a uint7, one byte for the BYTES - // tag, five bytes for the length of the byte array as an int32, and - // one byte for the END tag - int size = MAX_PACKET_LENGTH - 10; - if(tooBig) size++; - assertTrue(size > Short.MAX_VALUE); - w.writeUint7((byte) 0); - w.writeBytes(new byte[size]); + w.writeListStart(); + while(out.size() + serial.getSerialisedUniqueIdLength() + + serial.getSerialisedListEndLength() + + serial.getSerialisedStructEndLength() + < MAX_PACKET_LENGTH) { + w.writeBytes(TestUtils.getRandomId()); + } + if(tooBig) w.writeBytes(TestUtils.getRandomId()); + w.writeListEnd(); w.writeStructEnd(); assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH); return out.toByteArray(); } - private byte[] createRequest(byte[] bitmap) throws Exception { + private byte[] createEmptyRequest() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); Writer w = writerFactory.createWriter(out); w.writeStructStart(REQUEST); - w.writeUint7((byte) 0); - w.writeBytes(bitmap); + w.writeListStart(); + w.writeListEnd(); w.writeStructEnd(); return out.toByteArray(); } diff --git a/briar-tests/src/net/sf/briar/messaging/PacketWriterImplTest.java b/briar-tests/src/net/sf/briar/messaging/PacketWriterImplTest.java deleted file mode 100644 index 197bed53e73051c7f1582f1c3c5cdaec7a3288f8..0000000000000000000000000000000000000000 --- a/briar-tests/src/net/sf/briar/messaging/PacketWriterImplTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package net.sf.briar.messaging; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.BitSet; - -import net.sf.briar.BriarTestCase; -import net.sf.briar.TestDatabaseModule; -import net.sf.briar.TestLifecycleModule; -import net.sf.briar.api.messaging.PacketWriter; -import net.sf.briar.api.messaging.Request; -import net.sf.briar.api.serial.SerialComponent; -import net.sf.briar.api.serial.WriterFactory; -import net.sf.briar.clock.ClockModule; -import net.sf.briar.crypto.CryptoModule; -import net.sf.briar.db.DatabaseModule; -import net.sf.briar.messaging.duplex.DuplexMessagingModule; -import net.sf.briar.messaging.simplex.SimplexMessagingModule; -import net.sf.briar.serial.SerialModule; -import net.sf.briar.transport.TransportModule; -import net.sf.briar.util.StringUtils; - -import org.junit.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class PacketWriterImplTest extends BriarTestCase { - - // FIXME: This is an integration test, not a unit test - - private final SerialComponent serial; - private final WriterFactory writerFactory; - - public PacketWriterImplTest() { - Injector i = Guice.createInjector(new TestDatabaseModule(), - new TestLifecycleModule(), new ClockModule(), - new CryptoModule(), new DatabaseModule(), new MessagingModule(), - new DuplexMessagingModule(), new SimplexMessagingModule(), - new SerialModule(), new TransportModule()); - serial = i.getInstance(SerialComponent.class); - writerFactory = i.getInstance(WriterFactory.class); - } - - @Test - public void testWriteBitmapNoPadding() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PacketWriter w = new PacketWriterImpl(serial, writerFactory, out, - true); - BitSet b = new BitSet(); - // 11011001 = 0xD9 - b.set(0); - b.set(1); - b.set(3); - b.set(4); - b.set(7); - // 01011001 = 0x59 - b.set(9); - b.set(11); - b.set(12); - b.set(15); - w.writeRequest(new Request(b, 16)); - // STRUCT tag, struct ID 5, 0 as uint7, BYTES tag, length 2 as uint7, - // bitmap 0xD959, END tag - byte[] output = out.toByteArray(); - assertEquals("F3" + "05" + "00" + "F6" + "02" + "D959" + "F2", - StringUtils.toHexString(output)); - } - - @Test - public void testWriteBitmapWithPadding() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PacketWriter w = new PacketWriterImpl(serial, writerFactory, out, - true); - BitSet b = new BitSet(); - // 01011001 = 0x59 - b.set(1); - b.set(3); - b.set(4); - b.set(7); - // 11011xxx = 0xD8, after padding - b.set(8); - b.set(9); - b.set(11); - b.set(12); - w.writeRequest(new Request(b, 13)); - // STRUCT tag, struct ID 5, 3 as uint7, BYTES tag, length 2 as uint7, - // bitmap 0x59D8, END tag - byte[] output = out.toByteArray(); - assertEquals("F3" + "05" + "03" + "F6" + "02" + "59D8" + "F2", - StringUtils.toHexString(output)); - } -}